深入理解JavaScript系列(15) 函数(Functions)(4)


specialObject = {};
Scope = specialObject + Scope;
foo = new FunctionExpression;
foo.[[Scope]] = Scope;
specialObject.foo = foo; // {DontDelete}, {ReadOnly}
delete Scope[0]; // 从作用域链中删除定义的特殊对象specialObject


因此,在函数外部这个名称不可用的(因为它不在父作用域链中),但是,特定对象已经存储在函数的[[scope]]中,在那里名称是可用的。
但是需要注意的是一些实现(如Rhino)不是在特定对象中而是在FE的激活对象中存储这个可选的名称。Microsoft 中的执行完全打破了FE规则,它在父变量对象中保持了这个名称,这样函数在外部变得可以访问。
NFE 与SpiderMonkey
我们来看看NFE和SpiderMonkey的区别,SpiderMonkey 的一些版本有一个与特定对象相关的属性,它可以作为bug来对待(虽然按照标准所有的都那样实现了,但更像一个ECMAScript标准上的bug)。它与标识符的解析机制相关:作用域链的分析是二维的,在标识符的解析中,同样考虑到作用域链中每个对象的原型链。
如果我们在Object.prototype中定义一个属性,并引用一个”不存在(nonexistent)”的变量。我们就能看到这种执行机制。这样,在下面示例的”x”解析中,我们将到达全局对象,但是没发现”x”。但是,在SpiderMonkey 中全局对象继承了Object.prototype中的属性,相应地,”x”也能被解析。

复制代码 代码如下:


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


活动对象没有原型。按照同样的起始条件,在上面的例子中,不可能看到内部函数的这种行为。如果定义一个局部变量”x”,并定义内部函数(FD或匿名的FE),然后再内部函数中引用”x”。那么这个变量将在父函数上下文(即,应该在哪里被解析)中而不是在Object.prototype中被解析。

复制代码 代码如下:


Object.prototype.x = 10;
function foo() {
var x = 20;
// 函数声明
function bar() {
alert(x);
}
bar(); // 20, 从foo的变量对象AO中查询
// 匿名函数表达式也是一样
(function () {
alert(x); // 20, 也是从foo的变量对象AO中查询
})();
}
foo();


尽管如此,一些执行会出现例外,它给活动对象设置了一个原型。因此,在Blackberry 的执行中,上面例子中的”x”被解析为”10″。也就是说,既然在Object.prototype中已经找到了foo的值,那么它就不会到达foo的活动对象。
AO(bar FD or anonymous FE) -> no ->
AO(bar FD or anonymous FE).[[Prototype]] -> yes - 10
在SpiderMonkey 中,同样的情形我们完全可以在命名FE的特定对象中看到。这个特定的对象(按照标准)是普通对象——”就像表达式new Object()“,相应地,它应该从Object.prototype 继承属性,这恰恰是我们在SpiderMonkey (1.7以上的版本)看到的执行。其余的执行(包括新的TraceMonkey)不会为特定的对象设置一个原型。

复制代码 代码如下:


function foo() {
var x = 10;
(function bar() {
alert(x); // 20, 不上10,不是从foo的活动对象上得到的
// "x"从链上查找:
// AO(bar) - no -> __specialObject(bar) -> no
// __specialObject(bar).[[Prototype]] - yes: 20
})();
}
Object.prototype.x = 20;
foo();


NFE与Jscript
当前IE浏览器(直到JScript 5.8 — IE8)中内置的JScript 执行有很多与函数表达式(NFE)相关的bug。所有的这些bug都完全与ECMA-262-3标准矛盾;有些可能会导致严重的错误。
首先,这个例子中JScript 破坏了FE的主要规则,它不应该通过函数名存储在变量对象中。可选的FE名称应该存储在特定的对象中,并只能在函数自身(而不是别的地方)中访问。但IE直接将它存储在父变量对象中。此外,命名的FE在JScript 中作为函数声明(FD)对待。即创建于进入上下文的阶段,在源代码中的定义之前可以访问。

复制代码 代码如下:


// FE 在变量对象里可见
testNFE();
(function testNFE() {
alert('testNFE');
});
// FE 在定义结束以后也可见
// 就像函数声明一样
testNFE();


正如我们所见,它完全违背了规则。
其次,在声明中将命名FE赋给一个变量时,JScript 创建了两个不同的函数对象。逻辑上(特别注意的是在NFE的外部它的名称根本不应该被访问)很难命名这种行为。

复制代码 代码如下:

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

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