深入理解JavaScript系列(2) 揭秘命名函数表达式(5)


function createFn(){
return (function(){
var f;
if (true) {
f = function F(){
return 'standard';
};
}
else if (false) {
f = function F(){
return 'alternative';
};
}
else {
f = function F(){
return 'fallback';
};
}
// var F = null;
return f;
})();
}
var arr = [ ];
for (var i=0; i<10000; i++) {
arr[i] = createFn();
}


通过运行在Windows XP SP2中的任务管理器可以看到如下结果:

复制代码 代码如下:


IE6:
without `null`: 7.6K -> 20.3K
with `null`: 7.6K -> 18K
IE7:
without `null`: 14K -> 29.7K
with `null`: 14K -> 27K


如我们所料,显示断开引用可以释放内存,但是释放的内存不是很多,10000个函数对象才释放大约3M的内存,这对一些小型脚本不算什么,但对于大型程序,或者长时间运行在低内存的设备里的时候,这是非常有必要的。

关于在Safari 2.x中JS的解析也有一些bug,但介于版本比较低,所以我们在这里就不介绍了,大家如果想看的话,请仔细查看英文资料。
SpiderMonkey的怪癖
大家都知道,命名函数表达式的标识符只在函数的局部作用域中有效。但包含这个标识符的局部作用域又是什么样子的吗?其实非常简单。在命名函数表达式被求值时,会创建一个特殊的对象,该对象的唯一目的就是保存一个属性,而这个属性的名字对应着函数标识符,属性的值对应着那个函数。这个对象会被注入到当前作用域链的前端。然后,被“扩展”的作用域链又被用于初始化函数。
在这里,有一点十分有意思,那就是ECMA-262定义这个(保存函数标识符的)“特殊”对象的方式。标准说“像调用new Object()表达式那样”创建这个对象。如果从字面上来理解这句话,那么这个对象就应该是全局Object的一个实例。然而,只有一个实现是按照标准字面上的要求这么做的,这个实现就是SpiderMonkey。因此,在SpiderMonkey中,扩展Object.prototype有可能会干扰函数的局部作用域:

复制代码 代码如下:


Object.prototype.x = 'outer';
(function(){
var x = 'inner';
/*
函数foo的作用域链中有一个特殊的对象——用于保存函数的标识符。这个特殊的对象实际上就是{ foo: <function object> }。
当通过作用域链解析x时,首先解析的是foo的局部环境。如果没有找到x,则继续搜索作用域链中的下一个对象。下一个对象
就是保存函数标识符的那个对象——{ foo: <function object> },由于该对象继承自Object.prototype,所以在此可以找到x。
而这个x的值也就是Object.prototype.x的值(outer)。结果,外部函数的作用域(包含x = 'inner'的作用域)就不会被解析了。
*/
(function foo(){
alert(x); // 提示框中显示:outer
})();
})();


不过,更高版本的SpiderMonkey改变了上述行为,原因可能是认为那是一个安全漏洞。也就是说,“特殊”对象不再继承Object.prototype了。不过,如果你使用Firefox 3或者更低版本,还可以“重温”这种行为。
另一个把内部对象实现为全局Object对象的是黑莓(Blackberry)浏览器。目前,它的活动对象(Activation Object)仍然继承Object.prototype。可是,ECMA-262并没有说活动对象也要“像调用new Object()表达式那样”来创建(或者说像创建保存NFE标识符的对象一样创建)。 人家规范只说了活动对象是规范中的一种机制。
那我们就来看看黑莓里都发生了什么:

复制代码 代码如下:


Object.prototype.x = 'outer';
(function(){
var x = 'inner';
(function(){
/*
在沿着作用域链解析x的过程中,首先会搜索局部函数的活动对象。当然,在该对象中找不到x。
可是,由于活动对象继承自Object.prototype,因此搜索x的下一个目标就是Object.prototype;而
Object.prototype中又确实有x的定义。结果,x的值就被解析为——outer。跟前面的例子差不多,
包含x = 'inner'的外部函数的作用域(活动对象)就不会被解析了。
*/
alert(x); // 显示:outer
})();
})();


不过神奇的还是,函数中的变量甚至会与已有的Object.prototype的成员发生冲突,来看看下面的代码:

复制代码 代码如下:


(function(){
var constructor = function(){ return 1; };
(function(){
constructor(); // 求值结果是{}(即相当于调用了Object.prototype.constructor())而不是1
constructor === Object.prototype.constructor; // true
toString === Object.prototype.toString; // true
// ……
})();
})();


要避免这个问题,要避免使用Object.prototype里的属性名称,如toString, valueOf, hasOwnProperty等等。

JScript解决方案

复制代码 代码如下:

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

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