深入JavaScript作用域链
作者:Seiya
时间:2019年06月14日
前言
在 《深入 JavaScript 变量对象》 关于变量对象的描述中,我们已经知道一个执行上下文的数据(变量、函数声明和函数的形参)作为属性存储在变量对象中。同时我们也知道变量对象在每次进入上下文时创建,并填入初始值,值的更新出现在代码执行阶段。
对于每个执行上下文,都有三个重要属性:
变量对象(Variable object,VO)
作用域链(Scope chain)
this
这次我们对作用域链进行深入的讨论。
作用域链
定义
作用域链与一个执行上下文相关,变量对象的链用于在标识符解析中变量查找。
简单解释就是,当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
函数的生命周期
函数创建
之前的文章,我们讨论过,在进入上下文时函数声明放到变量/活动(VO/AO)对象中,来看一个例子:
var x = 10;
function foo() {
var y = 20;
alert(x + y);
}
foo(); // 30
我们看到变量“y”在函数“foo”中定义(意味着它在foo上下文的AO中),但是变量“x”并未在“foo”上下文中定义,相应地,它也不会添加到“foo”的AO中。乍一看,变量“x”相对于函数“foo”根本就不存在。
那么函数“foo”如何访问到变量“x”的?ECMAScript 实现了一种机制,通过函数内部的 [[scope]]
属性来访问一个更高一层上下文的变量对象。
[[scope]]
是所有父变量对象的层级链,处于当前函数上下文之上,在函数创建时存于其中。
重要:
[[scope]]
在函数创建时被存储--静态(不变的),直至函数销毁。即:函数可以永不调用,但 [[scope]]
属性已经写入,并存储在函数对象中。
注意:
与作用域链对比,[[scope]]
是函数的一个属性而不是上下文。
举个例子:
function foo() {
function bar() {
...
}
}
函数创建时,各自的 [[scope]]
属性为:
foo.[[scope]] = [
globalContext.VO
];
bar.[[scope]] = [
fooContext.AO,
globalContext.VO
];
函数激活
当函数激活时,进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用链的前端。如下所示;
Scope = [AO].concat([[Scope]])
我们用一个稍微复杂的例子描述作用域链的创建过程:
var x = 10;
function foo() {
var y = 20;
function bar() {
var z = 30;
alert(x + y + z);
}
bar();
}
foo(); // 60
具体执行分析:
第一步
:全局上下文的变量对象是:globalContext.VO === Global = { x: 10 foo: <reference to function> };
第二步
:在“foo”创建时,“foo”的[[scope]]
属性是:foo.[[Scope]] = [ globalContext.VO ]
第三步
:在“foo”激活时(进入上下文),“foo”上下文的活动对象是:fooContext.AO = { y: 20, bar: <reference to function> };
第四步
:“foo”上下文的作用域链为:fooContext.Scope = fooContext.AO + foo.[[Scope]] fooContext.Scope = [ fooContext.AO, globalContext.VO ]
第五步
:内部函数“bar”创建时,其[[scope]]
为:bar.[[Scope]] = [ fooContext.AO, globalContext.VO ]
第六步
:在“bar”激活时,“bar”上下文的活动对象为:barContext.AO = { z: 30 }
第七步
:“bar”上下文的作用域链为:barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.: barContext.Scope = [ barContext.AO, fooContext.AO, globalContext.VO ]
第八步
:此时,对“x”、“y”、“z”的标识符解析如下:- "x" -- barContext.AO // not found -- fooContext.AO // not found -- globalContext.VO // found - 10 - "y" -- barContext.AO // not found -- fooContext.AO // found - 20 - "z" -- barContext.AO // found - 30