深入理解JavaScript系列(14) 作用域链介绍(Scope(2)


var x = 10;
function foo() {
var y = 20;
function bar() {
var z = 30;
alert(x + y + z);
}
bar();
}
foo(); // 60


对此,我们有如下的变量/活动对象,函数的的[[scope]]属性以及上下文的作用域链:
全局上下文的变量对象是:

复制代码 代码如下:


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]] // 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]]属性相关的一些重要特征。
闭包
在ECMAScript中,闭包与函数的[[scope]]直接相关,正如我们提到的那样,[[scope]]在函数创建时被存储,与函数共存亡。实际上,闭包是函数代码和其[[scope]]的结合。因此,作为其对象之一,[[Scope]]包括在函数内创建的词法作用域(父变量对象)。当函数进一步激活时,在变量对象的这个词法链(静态的存储于创建时)中,来自较高作用域的变量将被搜寻。
例如:

复制代码 代码如下:


var x = 10;
function foo() {
alert(x);
}
(function () {
var x = 20;
foo(); // 10, but not 20
})();


我们再次看到,在标识符解析过程中,使用函数创建时定义的词法作用域--变量解析为10,而不是30。此外,这个例子也清晰的表明,一个函数(这个例子中为从函数“foo”返回的匿名函数)的[[scope]]持续存在,即使是在函数创建的作用域已经完成之后。
关于ECMAScript中闭包的理论和其执行机制的更多细节,阅读16章闭包。
通过构造函数创建的函数的[[scope]]
在上面的例子中,我们看到,在函数创建时获得函数的[[scope]]属性,通过该属性访问到所有父上下文的变量。但是,这个规则有一个重要的例外,它涉及到通过函数构造函数创建的函数。

复制代码 代码如下:


var x = 10;
function foo() {
var y = 20;
function barFD() { // 函数声明
alert(x);
alert(y);
}
var barFE = function () { // 函数表达式
alert(x);
alert(y);
};
var barFn = Function('alert(x); alert(y);');
barFD(); // 10, 20
barFE(); // 10, 20
barFn(); // 10, "y" is not defined
}
foo();


我们看到,通过函数构造函数(Function constructor)创建的函数“bar”,是不能访问变量“y”的。但这并不意味着函数“barFn”没有[[scope]]属性(否则它不能访问到变量“x”)。问题在于通过函构造函数创建的函数的[[scope]]属性总是唯一的全局对象。考虑到这一点,如通过这种函数创建除全局之外的最上层的上下文闭包是不可能的。
二维作用域链查找
在作用域链中查找最重要的一点是变量对象的属性(如果有的话)须考虑其中--源于ECMAScript 的原型特性。如果一个属性在对象中没有直接找到,查询将在原型链中继续。即常说的二维链查找。(1)作用域链环节;(2)每个作用域链--深入到原型链环节。如果在Object.prototype 中定义了属性,我们能看到这种效果。

复制代码 代码如下:


function foo() {
alert(x);
}
Object.prototype.x = 10;
foo(); // 10


活动对象没有原型,我们可以在下面的例子中看到:

复制代码 代码如下:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wdjzfx.html