词法 this

我们刚刚涵盖了一般函数遵守的四种规则。但是 ES6 引入了一种不适用于这些规则特殊的函数:箭头函数(arrow-function)。

箭头函数不是通过 function 关键字声明的,而是通过所谓的“大箭头”操作符:=>。与使用四种标准的 this 规则不同的是,箭头函数从封闭它的(函数或全局)作用域采用 this 绑定。

我们来展示一下箭头函数的词法作用域:

  1. function foo() {
  2. // 返回一个箭头函数
  3. return (a) => {
  4. // 这里的 `this` 是词法上从 `foo()` 采用的
  5. console.log( this.a );
  6. };
  7. }
  8. var obj1 = {
  9. a: 2
  10. };
  11. var obj2 = {
  12. a: 3
  13. };
  14. var bar = foo.call( obj1 );
  15. bar.call( obj2 ); // 2, 不是3!

foo() 中创建的箭头函数在词法上捕获 foo() 被调用时的 this,不管它是什么。因为 foo()this 绑定到 obj1bar(被返回的箭头函数的一个引用)也将会被 this 绑定到 obj1。一个箭头函数的词法绑定是不能被覆盖的(就连 new 也不行!)。

最常见的用法是用于回调,比如事件处理器或计时器:

  1. function foo() {
  2. setTimeout(() => {
  3. // 这里的 `this` 是词法上从 `foo()` 采用
  4. console.log( this.a );
  5. },100);
  6. }
  7. var obj = {
  8. a: 2
  9. };
  10. foo.call( obj ); // 2

虽然箭头函数提供除了使用 bind(..) 外,另外一种在函数上来确保 this 的方式,这看起来很吸引人,但重要的是要注意它们本质是使用广为人知的词法作用域来禁止了传统的 this 机制。在 ES6 之前,为此我们已经有了相当常用的模式,这些模式几乎和 ES6 的箭头函数的精神没有区别:

  1. function foo() {
  2. var self = this; // 词法上捕获 `this`
  3. setTimeout( function(){
  4. console.log( self.a );
  5. }, 100 );
  6. }
  7. var obj = {
  8. a: 2
  9. };
  10. foo.call( obj ); // 2

虽然对不想用 bind(..) 的人来说 self = this 和箭头函数都是看起来不错的“解决方案”,但它们实质上逃避了 this 而非理解和接受它。

如果你发现你在写 this 风格的代码,但是大多数或全部时候,你都用词法上的 self = this 或箭头函数“技巧”抵御 this 机制,那么也许你应该:

  1. 仅使用词法作用域并忘掉虚伪的 this 风格代码。

  2. 完全接受 this 风格机制,包括在必要的时候使用 bind(..),并尝试避开 self = this 和箭头函数的“词法 this”技巧。

一个程序可以有效地同时利用两种风格的代码(词法和 this),但是在同一个函数内部,特别是对同种类型的查找,混合这两种机制通常是自找很难维护的代码,而且可能是聪明过了头。