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).所谓"单线程",就是指一次只 ...
随机推荐
- 来,给Entity Framework热热身
先来看一下Entity Framework缓慢的初始化速度给我们更新程序带来的一种痛苦. 我们手动更新程序时通常的操作步骤如下: 1)把Web服务器从负载均衡中摘下来 2)更新程序 3)预热(发出一个 ...
- TODO:macOS上ThinkPHP5和Semantic-UI集成
TODO:macOS上ThinkPHP5和Semantic-UI集成 1. 全局安装 (on OSX via homebrew)Composer 是 homebrew-php 项目的一部分 2. 把X ...
- app开发外包注意事项,2017最新资讯
我们见过很多创业者,栽在这app外包上.很多创业者对于app外包这件事情不是特别重视,以为将事情交给app外包公司就完事了,实际上不是的.无论是从选择app外包公司还是签订合同.售后维护等各方面都有许 ...
- NET Core-TagHelper实现分页标签
这里将要和大家分享的是学习总结使用TagHelper实现分页标签,之前分享过一篇使用HtmlHelper扩展了一个分页写法地址可以点击这里http://www.cnblogs.com/wangrudo ...
- 最好的.NET开源免费ZIP库DotNetZip(.NET组件介绍之三)
在项目开发中,除了对数据的展示更多的就是对文件的相关操作,例如文件的创建和删除,以及文件的压缩和解压.文件压缩的好处有很多,主要就是在文件传输的方面,文件压缩的好处就不需要赘述,因为无论是开发者,还是 ...
- JavaScript基础知识总结(四)
JavaScript语法 八.函数 函数就是完成某个功能的一组语句,函数由关键字function + 函数名 + 加一组参数定义: 函数在定义后可以被重复调用,通常将常用的功能写成一个函数,利用函数可 ...
- JavaScript基础知识总结(一)
当我们接触一种新语言时,首先要先了解它,对它有一定的理论认识. 那么,什么是JavaScript呢? JavaScript是一种脚本语言,由web浏览器进行解释和执行.它包括ECMAScript.DO ...
- maven-sprigmvc-mybatis配置
pom.xml配置 <?xml version="1.0"?> <project xsi:schemaLocation="http://maven.ap ...
- Postman - 功能强大的 API 接口请求调试和管理工具
Postman 是一款功能强大的的 Chrome 应用,可以便捷的调试接口.前端开发人员在开发或者调试 Web 程序的时候是需要一些方法来跟踪网页请求的,用户可以使用一些网络的监视工具比如著名的 Fi ...
- mono for android学习过程系列教程(3)
服务 接着上一讲的内容,咱们继续来唠叨概念性的东西.服务,内容提供器,广播接收器等理论知识. 首先是服务,它不是一个可视化的组件或者视图.他是由我们开发人员来定义,可以一直一直运行 的工作单元.跟活动 ...