ECMAScript 允许创建内部函数,我们甚至能从父函数中返回这些函数,每个上下文拥有自己的变量对象:对于全局上下文,它是全局对象自身;对于函数,它是活动对象。
作用域链正是内部上下文所有变量对象(包括父变量对象)的列表。此链用来变量查询。即在上面的例子中,“bar”上下文的作用域链包括AO(bar)、AO(foo)和VO(global)。
上下文如下:activeExecutionContext = {
VO: {...}, // or AO
this: thisValue,
Scope: [ // Scope chain
// 所有变量对象的列表
// for identifiers lookup
]
};
Scope = AO + [[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:
};
在“foo”创建时,“foo”的[[scope]]属性是:
foo.[[Scope]] = [
globalContext.VO
];
在“foo”激活时(进入上下文),“foo”上下文的活动对象是:
fooContext.AO = {
y: 20,
bar:
};
“foo”上下文的作用域链为:
fooContext.Scope = fooContext.AO + foo.[[Scope]] // i.e.:
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
上面例子中,一个重要的属性[[scope]],[[scope]]是所有父变量对象的层级链,处于当前函数上下文之上,在函数创建时存于其中,更重要的一点是,[[scope]]在函数创建时被存储--静态(不变的),永远永远,直至函数销毁。即:函数可以永不调用,但[[scope]]属性已经写入,并存储在函数对象中。
下面用一个经典例子
var x = 10;
function foo() {
alert(x);
}
(function () {
var x = 20;
foo(); // 10, but not 20
})();
总结:Javacript中,函数才能创建一个作用域块也就是上下文,在函数创建的时候就已经确定了层级链,运行时才将相应的变量赋值并形成作用域链。
备注: 这里只有一类函数除外,那就是通过Function构造器创建的函数,因为其[[Scope]]只包含全局对象。