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).所谓"单线程",就是指一次只 ...
随机推荐
- 使用 Nodejs 搭建简单的Web服务器
使用Nodejs搭建Web服务器是学习Node.js比较全面的入门教程,因为要完成一个简单的Web服务器,你需要学习Nodejs中几个比较重要的模块,比如:http协议模块.文件系统.url解析模块. ...
- Windows平台分布式架构实践 - 负载均衡
概述 最近.NET的世界开始闹腾了,微软官方终于加入到了对.NET跨平台的支持,并且在不久的将来,我们在VS里面写的代码可能就可以通过Mono直接在Linux和Mac上运行.那么大家(开发者和企业)为 ...
- 阿里签名中URLEncode于C#URLEncod不同之处
问题 如上图所示,阿里云的PercentEncode 转换! 为 %21 PercentEncode 源码为: package com.aliyuncs.auth; import java.io.Un ...
- Electron使用与学习--(基本使用与菜单操作)
对于electron是个新手,下面纯属个人理解.如有错误,欢迎指出. 一.安装 如果你本地按照github上的 # Install the `electron` command globally ...
- 【SSM框架】Spring + Springmvc + Mybatis 基本框架搭建集成教程
本文将讲解SSM框架的基本搭建集成,并有一个简单demo案例 说明:1.本文暂未使用maven集成,jar包需要手动导入. 2.本文为基础教程,大神切勿见笑. 3.如果对您学习有帮助,欢迎各种转载,注 ...
- Android学习探索之Java 8 在Android 开发中的应用
前言: Java 8推出已经将近2年多了,引入很多革命性变化,加入了函数式编程的特征,使基于行为的编程成为可能,同时减化了各种设计模式的实现方式,是Java有史以来最重要的更新.但是Android上, ...
- Linux碎碎念
在学习Linux过程中,有许多有用的小技巧.如果放在纸质的笔记本上,平时查阅会相当不方便.现在以一种“碎碎念”的方式,汇集整理在此,目前还不是很多,但随着学习.工作的深入,后续会陆陆续续添加更多的小技 ...
- JQuery 选择器
选择器是JQuery的根基,在JQuery中,对事件的处理,遍历DOM和AJAX操作都依赖于选择器.如果能够熟练地使用选择器,不仅能简化代码,而且还可以事半功倍. JQuery选择器的优势 1.简洁的 ...
- 【iOS】Xcode8+Swift3 纯代码模式实现 UICollectionView
开发环境 macOS Sierra 10.12.Xcode 8.0,如下图所示: 总体思路 1.建立空白的storyboard用于呈现列表 2.实现自定义单个单元格(继承自:UICollectionV ...
- Asp.Net Core + Dapper + Repository 模式 + TDD 学习笔记
0x00 前言 之前一直使用的是 EF ,做了一个简单的小项目后发现 EF 的表现并不是很好,就比如联表查询,因为现在的 EF Core 也没有啥好用的分析工具,所以也不知道该怎么写 Linq 生成出 ...