前些日子,我专门去看了箭头函数,但是阮一峰作者中的箭头函数文章里有写到这样一句话:
“在定义对象方法的时候不适合使用箭头函数,因为对象不构成单独的作用域”,作者的原代码如下:
const cat = {
lives:9,
jumps:() => {
this.lives--;
}
}
// 原作者解释:cat.jumps()方法是一个箭头函数,这是错误的。调用cat.jumps(),如果是普通函数,该方法内部的this指向cat;如果携程上面那样的箭头函数,使得this指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致jumps箭头函数定义的时候作用域是全局作用域。
作者的那句话让我突然很困惑,因为我一直以为ES6中只要是{}就是一个块级作用域,但确实就是这样理解的。作者想表达的是:对象是因为箭头函数才不构成独单的作用域。
那么接下来将探讨以下几个问题:
一、执行上下文和作用域的关系是什么?
上下文中的代码在执行的时候,会创建变量对象的一个作用域链。作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。代码正在执行的上下文的变量对象始终位于作用域链的最前端。
1.1 执行上下文
1.1.1 什么是执行上下文
每个上下文都有一个关联的变量对象,而这个上下文中定义的所有变量和函数都存在于这个对象上。
在浏览器里,全局上下文是window对象,var所声明的变量和函数都会称为window对象的属性以及方法。但let和const的顶级声明不会定义在全局上下文中,但在作用域链解析上效果是一样的。全局上下文的变量对象始终时作用域链的最后一个变量对象。
1.1.2 执行上下文的作用、生成、销毁
变量或者函数上下文的作用:决定他们可以访问哪些数据,以及它们的行为。
销毁:在其所有代码都执行完毕后,包括定义在它上面的所有变量和函数(全局上下文在应用程序退出前才会被销毁,比如关闭网页或退出浏览器)。ECMAScript程序的执行流就是通过这个上下文栈进行控制的。
1.1.3 上下文的分类(用例子解释)
var color = "blue";
function changeColor() {
let anotherColor = "red";
function swapColors() {
let tempColor = anotherColor;
anotherColor = color;
color = tempColor;
// 这里可以访问 全局的color、anotherColor 和 tempColor
}
// 这里可以访问 全局的color 和 anotherColor,但访问不到 tempColor
swapColors();
}
// 这里只能访问 color
changeColor();
上面有三个上下文:全局上下文、changeColor()的局部上下文、swapColors()的局部上下文。
从上面不同上下文中可以看出:局部上下文首先会从自己的变量对象搜索变量和函数,搜不到会去搜索上一级的变量对象。【函数参数被认为是当前上下文的变量,因此也跟着上下文中的其他变量遵循相同的访问规则】
2.1 作用域
2.1.1 作用域是什么时候形成的?
function a() {
function b() {
var b = 1;
}
var a = 123;
b();
}
var c = 2;
a();
在a函数被定义时,生成以下作用域:
执行a函数,生成如下作用域:
能看到有个Activation Object对象(活动对象),这表示上下文是函数时,活动对象用作变量对象。活动对象最初只有一个定义变量:arguments【全局上下文没有这个变量】。
a函数执行时,b函数定义,b定义时,生成如下作用域,从图中可以看出,b在定义时的作用域直接指向a执行时生成的执行期上下文,它们共用相同的执行期上下文。
2.1.2 作用域链增强
上下文主要有全局上下文、函数上下文、eval()内部调用,但会有其他方式来增强作用域链。某些语句会导致在作用域链前端临时添加一个上下文,这个上下文在代码执行后会被删除。遇到下面任意一种情况都会出现这种现象:
1.try/catch语句的catch:创建一个新的变量对象,这个变量对象会包含要抛出的错误对象的声明。
2.wtih语句:会向作用域链前端添指定的对象;