this的指向问题
必要知识准备
::: 介绍 this 之前,先简单介绍 四个概念(普通函数、对象方法、构造函数、箭头函数),作为我们的知识储备,为后面我们介绍 this 做铺垫 :::
-
**普通函数:**函数是一段可以被重复调用的代码块,普通函数的声明方法基本结构很简单,我们都知道,就是下面的这种
function 函数名() {函数体}// 调用函数函数名() -
对象方法
什么是对象?
-
**概念:**对象也是 JS 数据类型的一种,和之前学习的数值类型、字符串类型、布尔类型是一样的。对象数据类型可以被理解成是一组 无序的 键值对的 集合,是属性的容器
-
键: 又称属性名,通常是一个字符串
-
值: 可以是任何数据类型,包括:数字、字符串、布尔值、数组、函数,甚至是另一个对象(嵌套)
-
**属性:**属性是成对出现的,包括属性名和属性值,它们之间使用英文
:分隔,多个属性之间使用英文**,**分隔
怎样创建一个对象呢?
最常用的一种就是使用花括号
**{ }**直接创建let 对象名 = {属性名1: 属性值1,属性名2: 属性值2,属性名3: 属性值3,属性名4: 函数,...}什么是方法?
- 属性值可以是任何数据类型,这里面有一个数据类型比较特殊,就是函数,如果一个属性的值是一个函数,那么我们称这个属性是这个对象的一个方法
由于普通函数和方法之间有点像,所以我们举个例子带大家区分一下
// 创建一个函数function fn() {console.log('111')}// 直接放在script标签里面,他就是一个普通函数function fn() {console.log('111')}// 将这个函数放进一个对象里面let obj = {fn:function() { // 是这个obj对象的方法console.log('111')}}怎样访问对象里面的属性呢?
-
创建了一个对象之后,我们怎么访问该怎样访问这个对象里面的属性呢?
-
声明对象并添加了若干属性后,可以使用
.或[]获得对象中属性对应的值, -
基本上写法就是 对象名.属性名 或者 对象名[‘属性名’]
-
如果我们需要为这个属性重新赋值或者添加一个属性,也很简单 对象名.属性名 = 新的属性值
-
-
我们平常写代码经常写的
-
// 获取DOM元素let oDiv = document.querySelector('div')// 给这个元素添加一个字体颜色为红色的样式oDiv.style.color = 'red'// style 就是 oDiv 的一个属性,这个属性的值其实也是一个对象,而 color 是 style 的一个属性上面的是不是很熟悉,其实当我们把 div 这个标签获取过来并赋给变量 oDiv 时,我们也相当于创建一个一个对象名为 oDiv 的对象,只是这个对象里面已经很多自带的属性和方法了,我们用的时候直接使用
.调用我们需要的属性,并给它重新赋值就行了怎样访问对象里面的方法呢?
基本上和访问属性的写法是一致的,只有一点不一样,我们都知道普通函数的调用方法,函数名() ,最后必须要加上一个小括号,这个函数的调用的方法是不论在哪里都不变的,前面讲到方法的属性值是函数,既然是函数,那我们也应该在属性名后面加上 () ,即 对象名.属性名()
let person = {name: '小红',age: 18,singing: function() {console.log('两只老虎,两只老虎,跑的快,跑的快...')},run: function () {console.log('我跑的非常快...')}}// 调用person对象中 singing 方法person.singing()// 调用person对象中的 run 方法person.run() -
-
构造函数
-
在前面我们学习了对象之后,应该能更好的理解构造函数,因为构造函数其实也是在创建对象
-
什么是构造函数?
-
本质上就是一个普通的函数,但是会有两个地方不太一样
-
命名方面上,构造函数的名称首字母要大写,这其实是一种普遍的命名的约定用来区别普通函数
-
与
**new**操作符一起使用:构造函数通过**new**操作符来调用,用于创建并初始化一个新对象。你们也可以这样理解,当一个函数使用**new**操作符调用时,它就成为了“构造函数”,即一个用于构造新对象的函数
-
-
-
构造函数的作用:创建对象
-
在没有构造函数之前,我们使用对象字面量创建单个对象
// 对象字面量创建单个对象let person1 = {name: "Alice",age: 25,greet :function() {console.log(Hello, my name is 11);}}person1.greet(); // 输出: Hello, my name is 11 -
但如果要创建多个结构相似(都有 name, age, greet 属性)的对象,重复写对象字面量会很麻烦,这时构造函数就派上用场了
-
构造函数就像一个“工厂模具”,可以批量生产具有相同属性和方法结构的对象
-
-
怎样使用构造函数呢?
- 第一步:定义构造函数,在其内部使用
**this**来定义未来的实例对象里面的属性和方法
创建一个函数(首字母要大写)
// 1. 定义构造函数 (首字母大写)// 这里相当于创建了一个模板function Person(name, age) {// 2. 使用 this 来添加属性this.name = name;this.age = age;}- 第二步:使用
**new**调用构造函数,使用**new**操作符来调用**Person**函数,它会按照构造函数的模板创建一个新的对象并返回它
- 第一步:定义构造函数,在其内部使用
// 使用 new 创建实例// 我们在这里调用这个模板let person1 = new Person('Alice', 25);let person2 = new Person('Bob', 30);console.log(person1.name); // 输出: Aliceconsole.log(person2.age); // 输出: 30我们可以看一个例子:
到这里,我想问大家,现在大家知道上面第二个图片里的变量 person1 和 person2 的值是什么数据类型吗?
-
-
箭头函数
-
箭头函数是 ES6 中引入的一种新的函数语法,它提供了一种更简洁的方式来编写函数
-
基本语法:
- 我们与传统的函数表达式对比来学
const add = function(a, b) {return a + b}// 完整写法(多行语句或需要显式返回对象时)const add = (a, b) => {return a + b}// 简洁写法(单行表达式,隐含 return)const add = (a, b) => a + b-
大家观察上面的两种写法有什么不同?
- 仔细观察,赋值等号左边的没有变化,变化在右边,箭头函数删去了 function ,变成了 => ,并且将 => 放到了 小括号 和 大括号 的中间
好啦,到这里我们的前置只知识准备就完成了,这就开始我们第二部分的介绍吧!
this 到底什么?
-
在正式开始介绍 this 到底是什么之前,这里有两个问题:
-
this 是不是变量?
-
this是一个特殊关键字(像function,if,new一样),不是可以声明的变量或对象的属性
-
-
**this 是对象吗?**学到现在,大家应该或多或少都应该见过
**this.sayName()**这种写法,那按照我们上面对于对象方法的学习,大家觉得 this 是对象吗?-
也不是,this 很多变,它本身不是一个对象,只是它的值实际根据是谁在调用它而变的,只是一般情况下 this 的值(它所指向的东西),总是一个对象
-
有一个类比,万能电视遥控器(this)
-
-
万能遥控器(this)本身不是电视
但按下遥控器的按钮(调用 this)的效果,取决于它当前指向并控制的是哪台电视
你可以用同一个遥控器(this),通过改变它的指向(不同的调用方式)来控制不同的电视(不同的对象)
-
所以 this 到底是什么呢?
-
一般来说,this 都是放在一个函数中使用的,调用这个函数也就相当于在调用这个 this ,
-
当我们总结上面的问题和类比例子后,其实可以用一句话总结,就是
**this**的值不是在我们定义函数时确定的,而是在我们调用这个函数时才被确定的 -
一个贴近生活的比喻:
-
想象你是一个员工(函数),你有一句话(函数体):“我正在为
this公司工作“ -
如果你在 A公司 的办公室里说这句话,
this就代表 A公司 -
如果你在 B公司 的办公室里说这句话,
this就代表 B公司你(函数)本身没变,但你所在的环境(调用者)变了,**this**的含义也就随之改变了
-
-
-
说多不如练多,这里有一个小问题,大家可以先试试能不能解答
const person = { name: '小明', sayHi: function() { console.log('你好,我是' + this.name) }}
person.sayHi()六个常见环境下的 this 指向问题
-
全局环境中的 this
- 全局环境就是在
<script></script>里(不在任何函数或对象内部),此时的 this 始终指向的是全局对象,在浏览器中就是 window
console.log(this) - 全局环境就是在
-
普通函数中的 this
当一个函数被直接调用时,要考虑两种情况
this在非严格模式下指向全局对象(window)
function fun() {console.log(this)}fun()// 我们实际上是在全局作用域下直接调用函数// fun() 实际上是window.fun(), 所以this -> window在严格模式下指向 undefined
JS严格模式:JavaScript 在语法和行为上存在一些模糊的特性,可能导致一些不易察觉的错误,为提高代码的质量和可维护性,JS 引入了严格模式,通过启用一些额外的规则,强制执行更严格的语法和行为,帮助提前发现和修复潜在bug。
// 演示严格模式下的情况function fu() {"use strict" // 在函数体中写一句 "use strict" ,就可以启用函数的严格模式console.log(this)}fu() // this 指向 undefined -
对像方法中的this
- 当函数作为对象的方法被调用时,this指向该方法所属的对象
let person = {name: "John",sayName: function () {console.log(this.name)}}person.sayName()let name = '卡卡';let cat = {name:'有鱼',eat1:{name:'年年',eat2:function(){console.log(this.name);}}}cat.eat1.eat2(); -
构造函数中的this
- 使用new关键字(实例化)调用函数时,该函数被当作构造函数,this会指向新创建的对象实例
// 构造函数function Person(name) {this.name = namethis.sayHello = function () {console.log("Hello, I'm " + this.name)}}// 创建实例let john = new Person("John");john.sayHello() // 输出 "Hello, I'm John",这里 this 指向 john 实例let john = {name : name,sayHello : function () {console.log("Hello, I'm " + this.name)}}// 构造函数function Person(name) {this.name = namethis.sayHello = function() {console.log(`你好,我是 ${this.name}`)}}// 创建实例const person1 = new Person('张三')person1.sayHello() -
事件处理中的this
- 在DOM事件处理函数中,this通常指向触发事件的元素
<button id="myButton">Click me</button><script>var button = document.getElementById("myButton")button.onclick = function () {console.log(this)}</script><button class = "btu">Click</button><script>let oBtu = document.querySelector('.btu')oBtu.addEventListenter('click',clickBtu)function clickBtu() {console.log(this)}</script> -
箭头函数中的this
-
箭头函数没有自己的this,它的 this 直接“捕获”或“继承”**外层作用域(它父级)**的 this 的值
-
前面介绍过普通函数 的
this是动态的,取决于如何被调用 -
而箭头函数 的
this是词法的,取决于定义时的上下文,且一旦定义就固定不变 -
词法的 = 写代码时候的位置决定的(this的出生地)
-
// 普通函数function outerFunction() { this.name = "Outer"
var innerFunction = function () { console.log(this.name) }
innerFunction()}
// 箭头函数function outerFunctionWithArrow() { this.name = "外层作用域的this"
var innerFunction = () => { console.log(this.name) }
innerFunction()}
// 一种情况new outerFunction()new outerFunctionWithArrow()
// 另一种调用情况outerFunction()outerFunctionWithArrow()改变 this 指向的方法
::: 由于箭头函数的this来自于继承,箭头函数无法使用以下三种方法改变this指向 :::
-
call() 方法
call() 方法附加在函数调用后面使用,可以忽略函数本身的 this 指向,然后将这个函数本身的 this 指向绑定到 call() 方法的第一个参数上面
函数.call(thisArg,arg1,arg2,...,argN)-
参数解析:
-
thisArg:就是你想将调用这个方法的函数本身的 this 指向绑定到哪个对象上面
-
arg1-argN:从参数 arg1 开始,依次是向函数传递参数
-
-
注意点:
- 使用 call() 方法时,相当于你调用了这个函数,会立刻执行这个函数
function greet() {console.log(this.animal, "的睡眠时间一般在", this.sleepDuration, "之间");}const obj = {animal: "猫",sleepDuration: "12 到 16 小时",};greet.call(obj); // 猫 的睡眠时间一般在 12 到 16 小时 之间 -
-
apply() 方法
和 call() 方法很相似,几乎一样
函数名.apply(thisArg,[arg1,arg2,...,argN])-
参数解析:
-
thisArg:同样为你想将这个函数本身的 this 指向绑定到哪个对象上面
-
[arg1,arg2,…,argN](主要区别点):一个 数组,数组里面的每一项依次是向函数传递的参数,这里可以直接写入一个数组,也可以写一个值为数组的变量
-
-
-
bind() 方法
和上面两个方法不一样,
**bind()**方法创建一个新函数并且还有返回值,使用**bind()**方法后不会立即执行函数,而是返回一个已经改变了 this 指向的函数函数名.bind(thisArg, arg1, arg2, ..,argN)-
参数解析:
-
thisArg:作用与前两个一致
-
arg1, arg2, ..,argN: 在调用函数时,插入到传入绑定函数的参数前的参数
-
-
返回值:
- 一个改变了 this 指向以后的 function 函数
-
const module = { x: 42, getX: function () { return this.x; },};
console.log(module.getX())
const unboundGetX = module.getX;//undefined 函数未被调用,存储的是一个值console.log(unboundGetX());
const boundGetX = unboundGetX.bind(module);console.log(boundGetX());总结及综合演练
::: this指向:
1、一般函数:谁调用函数,this就指向谁,没有调用者就指向全局对象Window
2、箭头函数:箭头函数不会创建 this,它的 this 继承自上层作用域中的 this :::
<button class = 'btu'>Click</button>
<script>console.log(this)
// 一个普通函数function fn() { console.log(this)}
// 创建一个对象let obj = { fn: fn}
// 直接调用fn()// 对象方法调用obj.fn()// 事件处理中调用let oBtu = document.querySelector('.btu')oBtu.addEventLister('click',fn)</script>// 构造函数function Fn(age) { this.name = 111 this.age = age, this.currentAge = function() { console.log(this.age) }}
let Fn1 = new Fn(16)Fn1.currentAge()// call()方法function Product(name, price) { this.name = name; this.price = price;}
function Food(name, price) { Product.call(this, name, price); this.category = "food";}
console.log(new Food("cheese", 5).name);-
代码解析:
-
关键点:Product.call(this, name, price)
-
这行代码的作用是:
-
在 Food 的上下文中调用 Product 构造函数
-
第一个参数 this 是新创建的 Food 实例
-
call() 方法将 Product 中的 this 绑定到传入的 Food 实例上
-
相当于在 Food 实例上设置 name 和 price 属性
-
-
执行过程:
-
new Food(“cheese”, 5) 创建 Food 实例
-
Food构造函数中的 this 指向这个新实例
-
Product.call(this, “cheese”, 5) 让 Product 构造函数在这个 Food 实例上工作
-
最终创建的 Food 对象包含:{name: “cheese”, price: 5, category: “food”}
-
-
const outer = { name: "Outer Object",
innerFunction: function () { const inner = { name: "Inner Object", nestedFunction: function () { console.log(this.name) } } inner.nestedFunction() }
}outer.innerFunction();Some information may be outdated