ES6 箭头函数中的 this?你可能想多了(翻译)

箭头函数=>无疑是ES6中最受关注的一个新特性了,经过它能够简写 function 函数表达式,你也能够在各类说起箭头函数的地方看到这样的观点——“=> 就是一个新的 function”。git

箭头函数的句法规则甚至早已延伸到各项标准和技术文档中去了,虽然它早已不稀奇,却给咱们一种刚刚发现的新鲜感。 

粉个人人都知道俺由于某些缘由不怎么喜欢 => 的语法,不过别担忧,本文并不是讲述我为什么不喜欢它,若是你对这个观点感兴趣,能够查看我《YDKJS:ES6 & Beyonf》一书的第二章es6

我想在这里理清一下箭头函数到底对 this 和 arguments 等东东作了些啥,事实上我在以前从未准确解释过这一点,对此感到有点愧疚,因而乎想洗白下本身。你能够在这里看到我对于该话题的第一次陈述。github

是否局部(Lexical)?闭包

包括我在内的许多人,都会这么描述箭头函数里 this 的行为:局部的 this。函数

什么意思呢?测试

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

foo.call( { id: 42 } );
// id: 42

这里的 => 箭头函数看起来把它内部的 this 绑定为父函数 foo() 里的 this。若是这个内部函数是一个常规的函数(声明或表达式),它的 this 将相似 setTimeout 如何调用函数同样被控制着。若是你对 this 绑定的规则还不清楚,能够查阅我《YDKJS:this & Object Prototypes》一书的第二章this

局部变量 thisspa

一个描述 this 行为观察的经常使用伎俩是:prototype

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”行为。

但这种观点并不正确,坑爹了。

箭头函数的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。

是的,这个版本的代码中咱们观测到的行为跟以前两段示例代码所要论述的同样,它更准确么?许多童鞋都认为 => 箭头函数就是这么工做的。

啧啧~图样图森破了~

生来局部

TC39的常客 Dave Herman 曾更仔细、准确地向我阐述过这个问题,但我很愧疚一直没能彻底了解他所陈述的含义,所以对于我往日不许确的言论我就更感歉意了,也更能接纳他人的观点。

Dave 主要对我这么说,“你说起的'局部 this'的描述很蹩脚,由于 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 的地方。

说白了跟其它局部变量的常规处理是一致的!

换句话说,正如同 Dave 说的同样,this 生来局部,并且一直都保持局部态。=>箭头函数并不会绑定一个 this 变量,它的做用域会如同寻常所作的同样一层层地去往上查找。

不只仅是this

若是你贸贸然地赞成了“箭头函数就是常规function的语法糖”这样的观点,那是不正确的,由于事实并不是如此——箭头函数里并不按常规支持 var self = this 或者 .bind(this) 这样的糖果。

那些错误的解释都是典型的“给对了答案却讲错了缘由”,就像你在高中代数课的测试上明明写对了答案,但老师仍会画圈圈告诉你用错方法了——如何解得答案才是最重要的!

另外,关于“=>箭头函数不绑定自身的 this,而容许局部做用域的方案来沿袭处理之”的正确描述,也解释了箭头函数的另外一个状况——它们在函数内部不走寻常路的孩子不只仅是 this。

事实上 =>箭头函数并不绑定 this,arguments,super(ES6),抑或 new.target(ES6)。

这是真的,对于上述的四个(将来可能有更多)地方,箭头函数不会绑定那些局部变量,全部涉及它们的引用,都会沿袭向上查找外层做用域链的方案来处理。

思考下这段代码:

function foo() {
   setTimeout( () => {
      console.log("args:", arguments);
   },100);
}

foo( 2, 4, 6, 8 );
// args: [2, 4, 6, 8]

这段代码中,=>箭头函数并无绑定 arguments,因此它会以 foo() 的 arguments 来取而代之,而 super 和 new.target 也是同样的状况。

总结

不要不经思考就轻易接受那些不许确的答案,不用知足于那些经过错误形式获取到的正确答案。

这关系到了事物是怎样做业的,以及你使用了怎样的心智模型(mental model),你会使用这种心智模型去分析、描述和调试其它的行为,若是你在一开始的时候就偏离了轨道,那么在以后你也只会一直停留在错误的轨道上。

我后悔当初没有更仔细地聆听 Dave 的观点,也好但愿当初本身木有发表过关于=>箭头函数的错误言论。我会在从此思考、提笔、传道JS的时候更加严格地确保其正确性,也会让本身更加当心谨慎。

 

原文 http://blog.getify.com/arrow-this/

donate

相关文章
相关标签/搜索