本文译自Dmitry Soshnikov的《ECMA-262-3 in detail》系列教程。其中会加入一些个人见解以及配图举例等等,来帮助读者更好的理解JavaScript。
前言
我们都知道,在变量对象的两章《从ECMAScript规范深度分析JavaScript(二):变量对象(上)》和《从ECMAScript规范深度分析JavaScript(三):变量对象(下)》中讲解了变量对象,执行期上下文数据(变量,函数声明,函数形参)作为变量对象的属性存储,并且,我们知道变量对象在每次进入上下文时被创建并且填充初始值,在代码执行阶段更新这些值。然而我们如何访问这些值呢?
本篇,我们将更进一步探讨和执行期上下文相关的的细节,就是作用域链(Scope chain)。
定义
如果想简洁的描述并且展示重要的点,作用域链和内部函数关系比较大。
我们知道,ECMAScript允许创建内部函数,并且我们甚至可以从父函数中将它们返回:
var x = 10;
function foo() {
var y = 20;
function bar() {
alert(x + y);
}
return bar;
}
foo()(); // 30
因此,了解到每个上下文都有自己的变量对象:全局上下文是全局对象本身,函数是激活对象。
对于内部上下文来讲,作用域链实际上是所有(父级)变量对象的集合,这个链用于变量查找。 即在上述的例子中,“bar”上下文的作用域链包含AO(bar),AO(foo)和VO(global)。我们来详细检验一下这个理论。
我们从定义开始,再通过例子来进一步深入讨论。
作用域链是一个和执行期上下文相关的变量对象链,用来标志符解析时的变量查找。
函数上下文的作用域链在函数调用时创建,并且由激活对象和这个函数的内部[[Scope]]属性组成。我们接下来会详细讨论函数的[[Scope]]属性
上下文的形式:
activeExecutionContext = {
VO: {
...}, // or AO
this: thisValue,
Scope: [ // Scope chain
// list of all variable objects
// for identifiers lookup
]
};
Scope的定义是:
Scope = AO + [[Scope]]
在我们的示例中可以将Scope和[[Scope]]展示为普通的ECMAScript数组:
var Scope = [VO1, VO2, ..., VOn]; // scope chain
我们还可以通过在每个链的链接上指向父作用域(这里指父变量对象)将结构视图展示为层级对象链,这对应我们之前在变量对象中讨论的一些实现__parent__的概念
var VO1 = {
__parent__: null, ... other data}; -->
var VO2 = {
__parent__: VO1, ... other data}; -->
// etc.
但是,使用一个数组来表示一个作用域链更方便一些,所以我们将使用这种方法。此外,规范声明抽象为“a scope chain is a list of objects”,而在实现层面上可以使用通过__parent__特性的层级链的方法。数组是抽象表示列表概念的很好的选择。
AO+[[Scope]]的结合以及标识符解析的过程,我们将在下面讨论,这和函数的生命周期相关。
函数的生命周期
函数生命周期可以分为创建阶段和激活阶段(调用阶段),让我们详细讨论它们
1、函数创建阶段
众所周知,函数声明在进入上下文阶段被放到变量/激活对象(VO/AO),让我们来看一下全局上下文中一个变量和函数声明的例子(这里变量对象就是全局对象自身):
var x = 10;
function foo() {
var y = 20;
alert(x + y);
}
foo(); // 30
在函数激活时,我们看到正确的(预期的)结果- 30。然而,这有一个非常重要的特性。
在此之前,我们只讨论了当前上下文的变量对象,这里我们看到,y变量在foo中定义(这意味着在foo上下文的AO中),但是变量x没有在foo上下文中定义并且也没有被添加到foo的AO中。乍一看上去变量x根本不存在于foo函数,但我们接下来会看到,这只是也只是乍一看上去不存在。我们看到f