![bdbed2f693f37f30df3a22c04ad92e50.png](https://i-blog.csdnimg.cn/blog_migrate/d9d26ec7b51dcc4cc467d936fe68caab.png)
本文缘起于 箭头函数中this的指向,《ECMAScript6入门》勘误 ,本是此文下的两则评论。无奈作者删评关评,遂为此写一文。
原文作者通过各种情况的试探,得出一个结论“箭头函数中的this指向箭头函数定义时所在作用域中的this”。这种说法其实不算错,但和其他类似讨论的文章一样,将箭头函数中的 this 作为一个特殊的规则去记忆,并没有深入原理,于是将结论复杂化了。然而,lambda 表达式中 this 的设计是很符合逻辑的,理解的 lambda 的意义,就明白了为什么会这样,并不需要这种死记硬背。
词法作用域和动态作用域
在讨论 this 的指向这个问题的时候,本质上是对作用域的讨论。于是乎,首先需要明晰词法作用域和动态作用域这两个概念。
let
问,两次打印分别输出什么。答,均是 1 。明白了这个问题就明白了词法作用域。
函数 foo,定义于全局,在全局执行了一次,在函数 bar 内部执行了一次。执行 foo 的时候,访问了 a 这个变量,先会在函数内部查找,但 foo 内部并没有声明 a ,于是在定义的位置向上查找。逐次类推,直到全局,否则报错。foo 定义于全局,于是乎返回了全局变量 a 的值。至于第二次在 bar 内部执行的时候,由于规则是在定义处向上查找,bar 内的局部变量 a 对于 foo 而言并没有什么影响。
对于词法作用域而言,一旦函数定义,所有变量都是确定的,不会因执行的位置/方式而改变。但是当访问 this 的时候,情况却大不一样了。
调用一个函数有四个方式:
function
在这四个方式下,this 的指向均不同。以函数方式调用,this 指向 window;以构造函数的方式调用,this 指向所构造的实例对象;以对象方法的方式调用,this 指向其所在的对象;通过它们的 apply/call 方法间接调用,this 的指向取决于传入的参数。而这就是 JavaScript 中的动态作用域。
关于 lambda 表达式
lambda 表达式相对于函数,其实主要拥有两个特征。其一,能捕获外部变量;其二,能作为值传递。在 JS 中,对于一般的函数其实拥有这两个特征。函数的词法作用域保证了函数可以进行捕获外部变量,而函数是一个对象则让函数可以作为值进行传递。但在其他的语言中或许并不是这样,所以有一种论调就是在 js 中 lambda 表达式其实就是一个语法糖罢了,没什么意义。
慢着,在 JS 中能真的捕获所有的外部变量吗?似乎并不是这样,首先 js 中任何一个函数都有一个固定的变量 arguments 来表示这个函数的入参,次之除了词法作用域外还存在动态作用域 ,于是 this 这个值也没办法捕获了。
JS 中为了让函数更为符合 lambda 表达式的定义,于是去掉了其中的动态作用域以及 arguments。于是有了如今 lambda 表达式的设计,当访问 this/arguments 的时候,会根据词法作用域的规则去查找——“在定义的位置向上查找,逐次类推,直到全局,否则报错”。
总而言之
很多人通过各种情况试探出的拗口结论——“箭头函数中的this指向箭头函数定义时所在作用域中的this”,本是管中窥豹。将 lambda 表达式中的 this 的指向作为 this 又一一种特殊情况死记硬背的行为可以休矣。 lambda 表达式中的 this 之所以如此表现如此仅仅只是因为 lambda 表达式中不存在动态作用域而套用了词法作用域罢了。当调试一个 lambda 表达式的时候,只要知道出现 this 跟一般的变量/常量标识符的查找的方式是一样的就行了。