ES6 箭头函数中的 this?你可能想多了(翻译)
箭头函数=>无疑是ES6中最受关注的一个新特性了,通过它可以简写 function 函数表达式,你也可以在各种提及箭头函数的地方看到这样的观点——“=> 就是一个新的 function”。
粉我的人都知道俺因为某些原因不怎么喜欢 => 的语法,不过别担心,本文并非讲述我为何不喜欢它,如果你对这个观点感兴趣,可以查看我《YDKJS:ES6 & Beyonf》一书的第二章。
我想在这里理清一下箭头函数到底对 this 和 arguments 等东东做了些啥,事实上我在之前从未准确解释过这一点,对此感到有点愧疚,于是乎想洗白下自己。你可以在这里看到我对于该话题的第一次陈述。
是否局部(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
一个描述 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”行为。
但这种观点并不正确,坑爹了。
箭头函数的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/

ES6 箭头函数中的 this?你可能想多了(翻译)的更多相关文章
- es6箭头函数中this
普通函数: $scope.$on('$stateChangeSuccess',function(){this.list = this.getList();}); 箭头函数: $scope.$on('$ ...
- 深入理解ES6箭头函数中的this
简要介绍:箭头函数中的this,指向与一般function定义的函数不同,比较容易绕晕,箭头函数this的定义:箭头函数中的this是在定义函数的时候绑定,而不是在执行函数的时候绑定. 1.何为定义时 ...
- es6箭头函数 this 指向问题
es5中 this 的指向 var factory = function(){ this.a = 'a'; this.b = 'b'; this.c = { a:'a+', b:function(){ ...
- ES6之箭头函数中的this
在讲箭头函数中的this之前我们先介绍一下普通函数中的this. 普通函数中的this: (1)this指向它的直接调用者 (2)默认的,非严格模式下,没找到直接调用者则指向window ( ...
- es6 箭头函数(arrow function) 学习笔记
箭头函数有两个好处. 1.他们比传统函数表达式简洁. const arr = [1, 2, 3]; const squares = arr.map(x => x * x); // 传统函数表达式 ...
- es6箭头函数讲解
es6箭头函数的用法 箭头函数是es6的一种函数的简写方法. 如下: var f = v = > v; //等同于 var f = function(v){ return v; } var su ...
- js中的this和箭头函数中的this
一.ES6 允许使用"箭头"(=>)定义函数. // var f = v => v;// 上面的箭头函数等同于: // var f = function(v) {// ...
- ES6 — 箭头函数
一 为什么要有箭头函数 我们在日常开发中,可能会需要写类似下面的代码 const Person = { 'name': 'little bear', 'age': 18, 'sayHello': fu ...
- 前端分享----JS异步编程+ES6箭头函数
前端分享----JS异步编程+ES6箭头函数 ##概述Javascript语言的执行环境是"单线程"(single thread).所谓"单线程",就是指一次只 ...
随机推荐
- redux-amrc:用更少的代码发起异步 action
很多人说 Redux 代码多,开发效率低.其实 Redux 是可以灵活使用以及拓展的,经过充分定制的 Redux 其实写不了几行代码.今天先介绍一个很好用的 Redux 拓展-- redux-amrc ...
- 【翻译】MongoDB指南/CRUD操作(一)
[原文地址]https://docs.mongodb.com/manual/ MongoDB CRUD操作(一) 主要内容:CRUD操作简介,插入文档,查询文档. CRUD操作包括创建.读取.更新和删 ...
- Could not create SSL connection through proxy serve-svn
RA layer request failedsvn: Unable to connect to a repository at URL xxxxxx 最后:Could not create SSL ...
- Android Ormlite 学习笔记1 -- 基础
Ormlite 是一个开源Java数据实体映射框架.其中依赖2个核心类库: 1.ormlite-android-4.48.jar 2.ormlite-core-4.48.jar 新建项目,引用上面2个 ...
- 基于ASP.NET/C#开发国外支付平台(Paypal)学习心得。
最近一直在研究Paypal的支付平台,因为本人之前没有接触过接口这一块,新来一家公司比较不清楚流程就要求开发两个支付平台一个是支付宝(这边就不再这篇文章里面赘述了),但还是花了2-3天的时间通 ...
- Android 工具-adb
Android 工具-adb 版权声明:本文为博主原创文章,未经博主允许不得转载. Android 开发中, adb 是开发者经常使用的工具,是 Android 开发者必须掌握的. Android D ...
- jquery中的$(document).ready(function() {});
当文档载入时执行function函数里的代码, 这部分代码主要声明,页面加载后 "监听事件" 的方法.例如: $(document).ready( $("a") ...
- php安装threads多线程扩展
php5.3或以上,且为线程安全版本.apache和php使用的编译器必须一致.通过phpinfo()查看Thread Safety为enabled则为线程安全版.通过phpinfo()查看Compi ...
- 我的MYSQL学习心得(七) 查询
我的MYSQL学习心得(七) 查询 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得(四) 数据类 ...
- Xamarin中使用DatePickerDialog的相关问题
在Xamarin中在使用Datepicker的时候,一般情况下只需要在对应的按钮或其他控件的点击事件中使用如下语句即可完成: EditText etBirthday = FindViewById< ...