词法 this
我们刚刚涵盖了一般函数遵守的四种规则。但是 ES6 引入了一种不适用于这些规则特殊的函数:箭头函数(arrow-function)。
箭头函数不是通过 function
关键字声明的,而是通过所谓的“大箭头”操作符:=>
。与使用四种标准的 this
规则不同的是,箭头函数从封闭它的(函数或全局)作用域采用 this
绑定。
我们来展示一下箭头函数的词法作用域:
function foo() {
// 返回一个箭头函数
return (a) => {
// 这里的 `this` 是词法上从 `foo()` 采用的
console.log( this.a );
};
}
var obj1 = {
a: 2
};
var obj2 = {
a: 3
};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, 不是3!
在 foo()
中创建的箭头函数在词法上捕获 foo()
被调用时的 this
,不管它是什么。因为 foo()
被 this
绑定到 obj1
,bar
(被返回的箭头函数的一个引用)也将会被 this
绑定到 obj1
。一个箭头函数的词法绑定是不能被覆盖的(就连 new
也不行!)。
最常见的用法是用于回调,比如事件处理器或计时器:
function foo() {
setTimeout(() => {
// 这里的 `this` 是词法上从 `foo()` 采用
console.log( this.a );
},100);
}
var obj = {
a: 2
};
foo.call( obj ); // 2
虽然箭头函数提供除了使用 bind(..)
外,另外一种在函数上来确保 this
的方式,这看起来很吸引人,但重要的是要注意它们本质是使用广为人知的词法作用域来禁止了传统的 this
机制。在 ES6 之前,为此我们已经有了相当常用的模式,这些模式几乎和 ES6 的箭头函数的精神没有区别:
function foo() {
var self = this; // 词法上捕获 `this`
setTimeout( function(){
console.log( self.a );
}, 100 );
}
var obj = {
a: 2
};
foo.call( obj ); // 2
虽然对不想用 bind(..)
的人来说 self = this
和箭头函数都是看起来不错的“解决方案”,但它们实质上逃避了 this
而非理解和接受它。
如果你发现你在写 this
风格的代码,但是大多数或全部时候,你都用词法上的 self = this
或箭头函数“技巧”抵御 this
机制,那么也许你应该:
仅使用词法作用域并忘掉虚伪的
this
风格代码。完全接受
this
风格机制,包括在必要的时候使用bind(..)
,并尝试避开self = this
和箭头函数的“词法 this”技巧。
一个程序可以有效地同时利用两种风格的代码(词法和 this
),但是在同一个函数内部,特别是对同种类型的查找,混合这两种机制通常是自找很难维护的代码,而且可能是聪明过了头。