先解释一下为什么已经两个星期没有更新关于this的后半部分吧,这两个星期笔者有一次打了18个小时麻将(从早上10点打到第二天早上5点,中途就吃了个晚饭)然后加上这两周有一些考试就没写关于js中this的后半部分。
今天终于有时间来写了,掘友们可以先去看看这篇文章从零开始详解js中的this(上) 再来看本篇,本文将详细讲述this的优先级、绑定例外及this的词法。
this的优先级
我们已经了解了函数调用中this绑定的四条规则,我们只需要找到函数的调用位置并判断应该使用哪条规则就好啦,但如果某个调用位置同时出现多条规则怎么办?为了解决这个问题我们不得不给这些规则设定优先级。比较之前我们先来回顾一下它的四种绑定规则
this的四种绑定规则
- 默认绑定 :当函数独立调用时,
this
指向全局对象(浏览器中是window
,严格模式下是undefined
)。 - 隐式绑定 :当函数作为对象的方法调用时,
this
指向调用该方法的对象。 - 显式绑定 :使用
call()
、apply()
或bind()
时,可以显式指定this
的值。 - new绑定 :使用
new
关键字创建对象时,this
指向新创建的对象实例。
比较方式
我们可以通过以下几种情况来比较这些规则的优先级:
1. 默认绑定 vs 隐式绑定
javascript
function foo() {
console.log(this.a);
}
var a = 2;
var obj = {
a: 3,
foo: foo
};
foo(); //2,默认绑定
obj.foo(); // 3,隐式绑定
在这个例子中,foo()
是独立调用的,因此this
指向全局对象,输出2
;而obj.foo()
是作为对象的方法调用的,因此this
指向obj
,输出3
。
注意
上面的代码如果在node中运行就不是这个结果了,第一个foo()就会打印出undefined,这其实是因为环境问题导致的,在node里全局变量 a
会自动成为 global
对象的属性。 当 foo()
被调用时,this
指向 global
对象。 但是,Node.js 的 global
对象并没有直接继承全局变量 a
。因此,this.a
为 undefined
。
2. 隐式绑定 vs 显式绑定
javascript
function foo() {
console.log(this.a);
}
var obj1 = {
a: 2
};
var obj2 = {
a: 3,
foo: foo
};
var bar = obj2.foo;
bar.call(obj1); // 2,显式绑定
在这个例子中,虽然bar
是从obj2
中获取的,但通过call()
显式指定了this
为obj1
,因此输出2
。
3. 显式绑定 vs new绑定
javascript
function foo(a) {
this.a = a;
}
var obj = {};
var bar = foo.bind(obj);
bar(2); // obj.a = 2,显式绑定
new bar(3); // 创建了一个新的对象实例,new绑定优先
console.log(obj.a); // 2
在这个例子中,bar
是通过bind()
显式绑定了obj
,由结果可以看出在使用new
时,new
绑定优先,创建了一个新的对象实例,而不是修改obj
。
4. new绑定 vs 默认绑定
javascript
function Foo() {
console.log(this instanceof Foo);
}
Foo(); // false,默认绑定
new Foo(); // true,new绑定
在这个例子中,Foo()
是独立调用的,因此this
指向全局对象,instanceof
检查失败;而new Foo()
是通过new
调用的,this
指向新创建的对象实例,instanceof
检查成功。
通过上面几个例子可以看出,this
的绑定规则有明确的优先级顺序。这四种绑定规则的优先级从高到低依次为:
- new绑定
- 显式绑定
- 隐式绑定
- 默认绑定
绑定例外
在 js 中,this
的绑定规则是理解函数调用上下文的关键。然而,有些情况下 this
的绑定行为并不遵循常规的默认、隐式、显式和 new
绑定规则。这些例外情况可能会导致意外的行为。下面一起来看看吧
箭头函数中的 this
箭头函数是 ES6 引入的一种新语法,它改变了 this
的绑定方式。与普通函数不同,箭头函数没有自己的 this
,而是继承自外层作用域(通常是定义它的上下文)。
javascript
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 }); // 输出: id: 42
在这个例子中,setTimeout
内部的箭头函数并没有创建新的 this
绑定,而是继承了外层 foo
函数的 this
值。因此,输出的是 42
而不是全局变量 id
的值 21
。
严格模式下的 this
在严格模式下,this
的默认绑定不再是全局对象,而是 undefined
。这可以避免一些潜在的错误。
javascript
function foo() {
'use strict';
console.log(this.a);
}
var a = 2;
foo(); // 输出: TypeError: Cannot read property 'a' of undefined
在严格模式下,this
默认绑定为 undefined
,因此尝试访问 this.a
会导致 TypeError
。
bind
方法的限制
虽然 bind
可以创建一个新的函数并永久绑定 this
,但需要注意的是,bind
创建的新函数不能被再次绑定或修改 this
。
javascript
function foo() {
console.log(this.a);
}
var obj1 = { a: 2 };
var obj2 = { a: 3 };
var bar = foo.bind(obj1);
bar(); // 输出: 2
bar.call(obj2); // 输出: 2,bind 后无法再改变 this
在这个例子中,bind
创建的 bar
函数已经绑定了 obj1
,即使后续使用 call
或 apply
尝试改变 this
,也不会生效。
this词法
箭头函数与词法 this
箭头函数是 ES6 引入的一种新语法形式,它没有自己的 this
绑定,而是继承自外层作用域(通常是定义它的上下文)。这种绑定方式被称为 词法 this
,即 this
的值是在函数定义时确定的,而不是在调用时确定的。
javascript
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 }); // 输出: id: 42
这个例子又来了,我们可以看到,setTimeout
内部的箭头函数并没有创建新的 this
绑定,而是继承了外层 foo
函数的 this
值。因此,输出的是 42
而不是全局变量 id
的值 21
。
词法 this
的优势
词法 this
解决了许多传统函数中常见的 this
绑定问题,特别是在回调函数和异步操作中。它使得代码更加简洁和直观,避免了不必要的 that
或 self
变量的使用。
javascript
function Person(name) {
this.name = name;
this.age = 29;
setInterval(() => {
console.log(`Name: ${this.name}, Age: ${this.age}`);
}, 1000);
}
var p = new Person('Kyle');
// 每秒输出: Name: Kyle, Age: 29
在这个例子中,箭头函数内部的 this
继承自 Person
构造函数的实例,因此可以直接访问 name
和 age
属性,而不需要额外的绑定操作。
箭头函数与 this
绑定的限制
尽管箭头函数简化了 this
绑定,但也有一些限制:
- 箭头函数不能用作构造函数,因此不能使用
new
关键字。 - 箭头函数没有自己的
arguments
对象,但可以通过剩余参数(rest parameters)来替代。 - 箭头函数没有自己的
this
,因此不能使用call
、apply
或bind
来改变this
的绑定。
javascript
function foo() {
'use strict';
return () => {
console.log(this.a);
};
}
var a = 2;
var obj = { a: 3 };
var bar = foo.call(obj);
bar(); // 输出: undefined (因为 foo 的 this 是 obj,但箭头函数的 this 是全局对象)
在这个例子中,虽然 foo
的 this
绑定到了 obj
,但箭头函数的 this
继承自外层作用域,即全局对象,因此输出 undefined
。
注意事项
在实际开发中,了解 this
的词法绑定规则可以帮助我们编写更健壮和可预测的代码。特别是在处理回调函数、事件处理器和异步操作时,箭头函数可以显著减少 this
绑定错误的发生。请看下面这段代码:
javascript
// 使用箭头函数解决 this 绑定问题
function Person(name) {
this.name = name;
this.age = 29;
this.growOlder = function() {
setTimeout(() => {
this.age++;
console.log(`Age: ${this.age}`); // this 正确指向 Person 实例
}, 1000);
};
}
var p = new Person('Kyle');
p.growOlder(); // 每秒输出: Age: 30, Age: 31, ...
在这个例子中,使用箭头函数后,this
正确地指向了 Person
实例,从而解决了 this
绑定的问题。
如果这篇文章对你有些许帮助的话,麻烦帮我投个票吧~