arrow function this

March 15, 2018 by sylvenas

ES6中值得称赞的特性之一就是提供函数表达式缩写定义的箭头函数语法。你很难发现关于ES6(或者甚至甚少与其相关的)的一篇文章、会议演讲或者书都没有首要介绍=>是新的function

其中有一点是最让人困惑的,就是箭头函数内的this到底指向哪里?举个例子来说:

function foo() {
   setTimeout(() => {
      console.log("id:", this.id);
   },100);
}
foo.call( { id: 42 } ); // id: 42

这里的=>箭头函数似乎绑定了它的this到父函数foo()的this上,不过也仅仅是看上去如此罢了,那么到底是怎么回事呢?,我们把上面的代码稍微修改一下:

function foo() {
   var self = this;
   setTimeout(function() {
      console.log("id:", self.id);
   },100);
}
foo.call( { id: 42 } ); // id: 42

变量名self是一个绝对可怕的,有误导的名字。它意味着this是函数本身的引用。但它从来没有。var that = this语义上同样无益,尤其当有多个作用域生效的时候(that1,that2,…)。如果你想要一个恰当的名字,使用var context = this,因为那就是this真正的含义:一个动态的上下文

在上面这个片段中,你可以看到我们甚至没有在内部函数使用this。相反,我们回退到一个更可预见性的机制:词法变量。我们在外部函数声明一个变量self,然后简单的在内部函数引用它。

这种完全消除了this绑定规则(对于内部函数,也是)。取而代之的是仅仅依赖词法作用域规则,实际上是闭包。

最终的结果似乎和=>箭头函数一样,换句话说,这里的(不严谨的)含义是=>箭头函数有一个了类似词法环境/闭包机制的方式的“词法this”行为

在看一个例子:

function foo() {
   setTimeout(function() {
      console.log("id:", this.id);
   }.bind(this),100);
}
foo.call( { id: 42 } ); // id: 42

正如你看到的.bind(this),这里的内部函数被强制绑定到外部函数的this,也意味着不管setTimeout(...)如何调用函数,调用函数始终使用foo()函数使用的this。

是的,这个版本有着和先前两个代码片段同样可观察到的行为。因此,它更精确吗?很多人认为这就是=>箭头函数实际上运行原理。

然而上面的结论应该都是错误的,就像是高中的时候,虽然一个题我们得到了最终的结果,但是老师会提出我们的整个计算过程都是错误的!

关于箭头函数内的this的指向,正确的解释应该是:=>从不绑定自己的this,而是直接获取当前词法作用域上下文作为自己的this,换句话说就是既然箭头函数没有自己的this,但是当你在箭头函数内部使用this的时候,普通词法作用域的规则是生效的,引用被解析为包含最近的外部作用域定义的this。 看这段代码:

function foo() {
   return () => {
      return () => {
         return () => {
            console.log("id:", this.id);
         };
      };
   };
}
foo.call({ id: 42 })()()();   // id: 42

在这个片段中,你认为有多少个this绑定?大多数可能认为是4个,每个函数一个。 准确无误的来说,只有一个,是在foo()函数中。 相继嵌套的=>箭头函数没有声明自己的this,因此this.id简单的沿着作用域链解析直到找到foo(),第一个确切绑定this的函数。

和其他的普通的词法变量的处理方式是一样的。

如果你含糊不清地解释=>对this的行为,你最终认为箭头函数仅仅是function的语法糖……,这是非常危险的,它们显然不是,也不是var self = this或者.bind(this)的语法糖。

事实上,=>箭头函数不绑定thisargumentssuper(ES6)或者new.target(ES6)。 看下面的代码:

function foo() {
   setTimeout( () => {
      console.log("args:", arguments);
   },100);
}
foo( 2, 4, 6, 8 ); // args: [2, 4, 6, 8]

在这个片段中,arguments没有被=>限定,因此它被解析到foo()函数的argumentssupernew.target也是同样的结果。

所以为什么对箭头函数.bind({...})不能得到我们想要的修改函数内部this指向的结果呢?

如果你对=>箭头函数的this有不准确的理解,你不得不假设可以这样解释:在arrow function 中 this是不可改变的,虽然结论是正确的,但是思考过程确实错误的。

简单正确的答案是既然=>没有this,当然.bind(obj)没有什么可以操作!类似地,=>不能被new操作符调用。既然没有thisnew没有东西可以绑定。