探索前端的 this 指向
在前端开发中,this 是一个常见的概念。
它代表了当前执行上下文中的对象或函数,并且在不同的情况下,this 的指向也会有所不同。
本文将深入探讨 this
在前端开发中的应用场景以及不同情况下的指向规则,更好地理解和运用 this
指向。
小试牛刀
1、普通函数、箭头函数组合使用
1 | var name = "TOM" |
2、改变 this 指向
1 | var name = 'win'; |
别急,答案和解析逐步揭晓。
this
在 JavaScript
中,函数的 this
关键字与其他语言有一些不同。
它也在严格模式和非严格模式下有一些区别。
在大多数情况下,this
的值取决于函数的调用方式(运行时绑定)。this
不能在执行期间被赋值,在每次函数被调用时 this 的值也可能会不同。
语法
this
的值取决于它出现在哪个上下文中:函数、类、或者全局。
在非严格模式下,始终是对象的引用。当函数被调用时,
JavaScript
会自动设置this
的值。- 如果函数作为对象的方法调用,
this
指向该对象 - 如果函数独立调用,
this
指向全局对象
- 如果函数作为对象的方法调用,
在严格模式下,它可以是任何值。
this
的值不再是默认绑定到全局对象,而是根据调用方式和上下文来确定。- 如果函数作为对象的方法调用,this 将绑定到该对象
- 如果使用
call()
、apply()
或bind()
显示指定this
值,将绑定到相应的值 - 如果函数是使用构造函数调用,
this
将绑定到新创建的对象 - 对于箭头函数,没有自己的
this
绑定,而是集成了外部函数的this
值
函数上下文
函数中的 this
的值取决于函数的调用方式,可将 this
视为函数的隐藏参数,就像函数定义中声明的参数一样,当函数体被执行时,会创建这个绑定 this
。
对象方法调用:如果函数是作为对象的方法调用时,
this
指向调用该方法的对象。换句话说,如果函数调用形式为obj.f()
,那么this
指向obj
。1
2
3
4
5
6
7
8const obj = {
name: "John",
greet: function() {
console.log("Hello, " + this.name + "!"); // this 指向 obj 对象
}
};
obj.greet(); // 输出 "Hello, John!"函数调用:如果函数是作为普通函数调用时,this 指向全局对象。
1
2
3
4function sayHello() {
console.log("Hello, " + this.name + "!"); // this 指向全局对象
}
sayHello(); // 输出 "Hello, undefined!"(假设全局对象的 name 属性未定义)
回调
当函数被作为回调传递时,this
的值取决于回调的调用方式,这由 API 实现者决定。
通常情况下,回调函数会以 undefined
作为 this
值进行调用(直接调用而没有附加到任何对象上),这意味着如果函数是非严格模式的话,this
的值就是全局对象(globalThis
)。
迭代数组方法 和 Promise() 构造函数等都是这种情况。
1 | function logThis() { |
有些 API 允许你为回调的调用设置 this
值。
如:所有迭代数组方法以及相关的方法如 Set.prototype.forEach() 都接受一个可选的 thisArg
参数。
1 | [1, 2, 3].forEach(logThis, { name: "obj" }); |
偶尔,某些回调会以非 undefined
的值作为 this
进行调用。
如:JSON.parse() 的 reviver
参数和 JSON.stringify() 的 replacer
参数会以当前被解析/序列化属性所属的对象作为 this
进行调用。
箭头函数
箭头函数 通过继承封闭上下文的 this
值来简化函数的定义。
换句话说,它们不会创建自己的 this
绑定,而是将 this
捕获为函数创建时的值,无论如何调用函数,this
都将保持不变。
在全局代码中,无论是否使用严格模式,this
始终是 globalThis
,这是由于全局上下文的绑定:
1 | const globalObject = this; |
当使用 call()、apply() 或 bind() 调用箭头函数时,thisArg
参数会被忽略。但仍然可以使用这些方法传递其他参数。
区别:
call 和 apply 调用时候立即执行,bind 调用返回新的函数。
当需要传递参数时候,call 直接写多个参数,apply 将多个参数写成数组,
bind 在绑定时候需要固定参数时候,也是直接写多个参数。
1 | const obj = { name: "obj" }; |
构造函数
当函数作为构造函数使用(使用 new
关键字),它的 this
会绑定到正在构建的新对象上,而不管构造函数在哪个对象上被访问。this
的值将成为 new
表达式的值,除非构造函数返回另一个非原始值。
1 | function C() { |
在上面 C2 的例子中,由于构造过程中返回了一个对象,因此绑定到 this
的新对象被丢弃了。
(这实际上使语句 this.a = 37
; 成为无效代码。虽然它被执行了,但可以被消除而没有外部影响。)
super
在使用 super.method()
形式调用函数时,
方法函数内部的 this
的值与 super.method()
调用周围的 this
值相同,并且通常与 super
引用的对象不相等。
因为 super.method
不是像上述的对象成员访问一样,它是具有不同绑定规则的特殊语法。
详细示例,请参阅 super 的相关文档。
简而言之,super 可以用来访问和调用父类的内容。
类上下文
一个类可以拆分为两个上下文:静态和实例。
1 | class C { |
派生类构造函数
与基类构造函数不同,派生构造函数没有初始的 this
绑定。
调用 super() 会在构造函数内部创建一个 this
绑定。
其中,Base
是基类。
1 | this = new Base(); |
警告:在调用 super()
之前引用 this
将会抛出错误。
派生类必须在调用 super()
之前不返回任何值,除非构造函数返回一个对象(因此覆盖了 this 值),或者该类根本没有构造函数。
1 | class Base {} |
全局环境
在全局环境下,this
值取决于脚本执行的上下文中运行。this
指向全局对象(通常是 window 对象)。
指在浏览器环境中,全局作用域下使用 this 可以直接访问 window 对象的属性和方法。
在脚本的顶层,this
引用 globalThis 无论是否处于严格模式,这通常与全局对象相同。
1 | console.log(this === window); // true |
公布答案及解析,你的战绩如何?
小试牛刀 1
答案:TOM、Jerry
解析:
问题1:SayHi
函数返回一个新的匿名函数,所以主要看谁调用了它,没人具体的调用者,所以this 指向 window。
问题2:SayFoo
函数返回一个新的匿名箭头函数,所以主要看定义该箭头函数其父级的 this,其父级的 this 指向的是该函数的调用者,所以 this 指向 obj。
小试牛刀 2
答案:win。
解析:
使用箭头函数定义了对象 obj 的属性 a。
箭头函数不会绑定自己的 this 值,而是继承外层作用域的 this 值。
在全局作用域中,this 指向全局对象(例如浏览器中的 window 对象),所以 this.name 实际上是在全局作用域中查找 name 变量的值。
在 obj1 对象上使用了 call 方法来显式设置 obj.a 函数中的 this 值为 obj1,但由于箭头函数不受 this 绑定的影响,它仍然会继承全局作用域中的 this 值。
Ending…
要正确理解和使用 this,需要了解当前代码的执行上下文,合理运用 this 指向,使得代码更加灵活和易于维护。
了解更多请参考文档。
希望这篇文章对您理解和使用 this 有所帮助。
Title: 探索前端的 this 指向
Author: Amber
Date: 2023-09-01
Last Update: 2024-10-29
Blog Link: https://wyiyi.github.io/amber/2023/09/01/this/
Copyright Declaration: Copyright © 2022 Amber.