探索 this 的指向之谜
前言
在 JavaScript 的广阔天地里,this
关键字如同一把双刃剑,既能让你的代码流畅自如,也能因误用而让人一头雾水。本文将带领你穿越 this
的迷雾,深入探讨 this 的几种绑定规则、优先级以及箭头函数中的 this 行为,并通过代码示例进行解释说明帮助读者更好了解 (v^_^)v
在JavaScript中,this
关键字是一个非常重要的概念,它代表了函数执行时的上下文对象,使用this可以让对象中的函数有能力访问对象自己的属性。当我们要传递的参数越来越复杂时,显示传递上下文对象会让代码更混乱,这时使用this关键字来隐式传递对象引用可以显著提升代码质量,减少上下文参数的传递,让代码更好维护。例如下面的代码中我们用this关键字就可以用更优雅的方式来隐式传参:
/*// 显示传参
function identify(context) {
return context.name.toUpperCase() // toUpperCase()方法:将小写转成大写字符串
}
function speak(context) {
var greeting = 'Hello I am ' + identify(context)
console.log(greeting);
}
*/
// 用 this 隐式传参
function identify() {
return this.name.toUpperCase()
}
function speak() {
var greeting = 'Hello I am ' + identify(this)
console.log(greeting);
}
var me = {
name: 'hhh'
}
speak(me) // Hello I am HHH
this 的绑定规则
1. 默认绑定
当一个函数独立调用,不带任何修饰符时(即不是作为对象的方法被调用),this
指向全局对象(在浏览器环境中是window
,在严格模式下是undefined
)
下面代码如果在非严格模式下(即浏览器环境下)运行的得到的结果是 2。因为foo是独立调用的,函数中的this指向全局,在浏览器环境下,在全局通过var声明的变量相当于在 window 对象上添加了一个属性,所以this.a 相当于 window.a 也就是 2。
但是如果在严格模式下运行(例如node.js)得到的是undefined,因为node的全局是global空对象,全局声明的a并不会挂在上面,所以this.a相对于global.a,也就是undefined。
function foo() {
console.log(this.a); // this指向全局作用域,相当于打印window.a = 1 2
}
var a = 2
foo()
2. 隐式绑定
当函数的引用有上下文对象时(即当函数作为某个对象的属性被调用时),this
指向引用它的对象。
下列代码中foo在第7行没有独立调用,this指向foo的引用对象obj,所以this.a相对于obj.a,也就是1。
var obj = {
a: 1,
foo: function() {
console.log(this.a); // 1,this指向obj
}
};
obj.foo();
3. 隐式丢失
当一个函数被多个对象链式调用时,函数的this指向就近的那个对象。
下列代码中foo被obj引用,obj又被obj2引用,所以第13行是一个链式调用。遇到这种情况时我们不用管函数的对象在哪个词法作用域中,我们只要看该函数是被谁调用了,this遵循就近原则。据此我们知道this还是指向obj的,因此this.a相对于obj.a,也就是1。
var obj = {
a:1,
foo: foo // foo的引用
}
var obj2 = {
a:2,
obj: obj
}
function foo() {
console.log(this.a); // 1
}
obj2.obj.foo()
4. 显示绑定
通过call
、apply
和bind
方法,我们可以显式地设置函数调用的this
值。
-
call:call 方法调用一个函数,其具有一个指定的 this 值和参数的列表。例如在下面代码中可以执行
foo.call(obj, 2, 3)
语句来让foo中的this指向obj。 -
apply:apply 方法调用一个函数,其具有一个指定的 this 值,以及一个数组提供的参数。例如在下面代码中可以执行
foo.apply(obj, [2, 3])
语句来让foo中的this指向obj。 -
bind:bind 方法创建一个新的函数,在 bind 被调用时,这个新函数的 this 被指定为 bind 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
var obj = {
a: 1
}
function foo(x, y) {
console.log(this.a, x + y);
}
// 使用bind创建一个新函数bar,其this指向obj,并预置了第一个参数2
var bar = foo.bind(obj, 2);
// 调用bar,传入剩余的参数3
bar(3); // 输出: 1 5
// 如果在bind时直接传入所有参数,则调用时不需要再传入任何参数
var baz = foo.bind(obj, 2, 3);
baz(); // 输出: 1 5
5. new 绑定
使用new
关键字创建实例对象时,this 指向新创建的实例对象。
为了让大家更好了解new绑定,我将以下面代码为例来讲解一下 new 创建实例对象的过程。
1.创建一个新的空对象 var obj = {}; (对象名obj只是指代一下)
2.将这个新对象的内部 [[Prototype]] 链接到构造函数的 prototype 属性
3. 将构造函数中的 this 指向这个新对象obj
4. 执行构造函数中的代码,此时 this 指向新对象
5. 如果构造函数返回一个对象,则返回该对象;否则,返回步骤 1 中创建的新对象。
function Person() {
this.name = 'Tom';
}
var p = new Person();
console.log(p.name); // Tom
this绑定的优先级
当多种绑定规则同时作用时,this
的绑定优先级为:new
绑定 > 显式绑定 > 隐式绑定 > 默认绑定(严格模式下绑定到undefined
,否则绑定到全局对象)。
箭头函数中的 this
箭头函数不绑定自己的 this,它会捕获其所在上下文的 this 值作为自己的 this 值,这意味着在箭头函数中,this 的值由外层(非箭头)函数或全局对象决定。
下列代码中,���头函数 fn 在 obj 的 b 方法内部定义,因此它捕获了 b 方法执行时的 this 值,即 obj ,所以 this.a 相对于 obj.a,也就是1。
var obj = {
a:1,
b: function() {
const fn = () => {
console.log(this.a); // 1
}
fn()
}
}
obj.b()
结语
希望这篇文章能够帮助你更好地理解this的指向,感谢观看!`・ω・´)ゞ敬礼っ
转载自:https://juejin.cn/post/7398503284075200549