并不存在阻止您调用eval函数的限制,这些限制会被静态分析所忽略:仅仅是因为对eval的间接和直接引用都在全局作用域中运行。请注意,这是ES5与ES3的不同之处,ES3中对eval的直接和间接引用均在本地作用域中运行。因此,我不确定是否有任何基于此事实的优化。
一个明显的测试方法是将BigObject设置为一个非常大的对象,并在运行f0-f2后强制进行垃圾回收。(因为嘿,尽管我认为我知道答案,但测试总是更好的选择!)
所以……
测试
var closure;
function BigObject() {
var a = '';
for (var i = 0; i <= 0xFFFF; i++) a += String.fromCharCode(i);
return new String(a);
}
function f0() {
var x = new BigObject();
var y = 0;
closure = function(){ return 7; };
}
function f1() {
var x = new BigObject();
closure = (function(y) { return function(){return y++;}; })(0);
}
function f2() {
var x = new BigObject();
var y = 0;
closure = function(){ return y++; };
}
function f3() {
var x = new BigObject();
var y = 0;
closure = eval("(function(){ return 7; })");
}
function f4() {
var x = new BigObject();
var y = 0;
closure = (1,eval)("(function(){ return 7; })");
}
function f5() {
var x = new BigObject();
var y = 0;
closure = (function(){ return eval("(function(){ return 7; })"); })();
}
function f6() {
var x = new BigObject();
var y = 0;
closure = function(){ return eval("(function(){ return 7; })"); };
}
function f7() {
var x = new BigObject();
var y = 0;
closure = (function(){ return (1,eval)("(function(){ return 7; })"); })();
}
function f8() {
var x = new BigObject();
var y = 0;
closure = function(){ return (1,eval)("(function(){ return 7; })"); };
}
function f9() {
var x = new BigObject();
var y = 0;
closure = new Function("return 7;");
}
我已经为eval/Function添加了测试,因为这些也是有趣的案例。f5/f6之间的差异很有趣,因为基本上f5与f3完全相同,都是为闭包提供一个相同的函数;而f6只返回一些东西,一旦被评估,就会得到那个结果,由于eval尚未被评估,编译器不知道其中是否有x的引用。
SpiderMonkey
js> gc()
"before 73728, after 69632, break 01d91000\n"
js> f0()
js> gc()
"before 6455296, after 73728, break 01d91000\n"
js> f1()
js> gc()
"before 6455296, after 77824, break 01d91000\n"
js> f2()
js> gc()
"before 6455296, after 77824, break 01d91000\n"
js> f3()
js> gc()
"before 6455296, after 6455296, break 01db1000\n"
js> f4()
js> gc()
"before 12828672, after 73728, break 01da2000\n"
js> f5()
js> gc()
"before 6455296, after 6455296, break 01da2000\n"
js> f6()
js> gc()
"before 12828672, after 6467584, break 01da2000\n"
js> f7()
js> gc()
"before 12828672, after 73728, break 01da2000\n"
js> f8()
js> gc()
"before 6455296, after 73728, break 01da2000\n"
js> f9()
js> gc()
"before 6455296, after 73728, break 01da2000\n"
SpiderMonkey似乎对除了f3、f5和f6之外的所有东西都进行垃圾回收。
它尽可能地回收(即在可能的情况下回收y和x),除非任何仍然存在的函数的作用域链中存在直接eval调用。即使该函数对象本身已经被GC且不再存在,例如在f5中,理论上也可能回收x/y。
V8
gsnedders@dolores:~$ v8 --expose-gc --trace_gc --shell foo.js
V8 version 3.0.7
> gc();
Mark-sweep 0.8 -> 0.7 MB, 1 ms.
> f0();
Scavenge 1.7 -> 1.7 MB, 2 ms.
Scavenge 2.4 -> 2.4 MB, 2 ms.
Scavenge 3.9 -> 3.9 MB, 4 ms.
> gc();
Mark-sweep 5.2 -> 0.7 MB, 3 ms.
> f1();
Scavenge 4.7 -> 4.7 MB, 9 ms.
> gc();
Mark-sweep 5.2 -> 0.7 MB, 3 ms.
> f2();
Scavenge 4.8 -> 4.8 MB, 6 ms.
> gc();
Mark-sweep 5.3 -> 0.8 MB, 3 ms.
> f3();
> gc();
Mark-sweep 5.3 -> 5.2 MB, 17 ms.
> f4();
> gc();
Mark-sweep 9.7 -> 0.7 MB, 5 ms.
> f5();
> gc();
Mark-sweep 5.3 -> 5.2 MB, 12 ms.
> f6();
> gc();
Mark-sweep 9.7 -> 5.2 MB, 14 ms.
> f7();
> gc();
Mark-sweep 9.7 -> 0.7 MB, 5 ms.
> f8();
> gc();
Mark-sweep 5.2 -> 0.7 MB, 2 ms.
> f9();
> gc();
Mark-sweep 5.2 -> 0.7 MB, 2 ms.
除了 f3、f5 和 f6 之外,V8 在所有其他地方都会进行 GC。与上面的分析相同,这和 SpiderMonkey 相同。(但是请注意,数字不够详细,无法确定 y 是否在 x 不进行 GC 时被 GC,我没有深入研究。)
Carakan
我不会再次运行此代码,但毫无疑问,其行为与 SpiderMonkey 和 V8 相同。没有 JS shell 更难测试,但可以通过一些时间来完成。
JSC(Nitro)和 Chakra
在 Linux 上构建 JSC 很繁琐,而 Chakra 在 Linux 上无法运行。我认为 JSC 与上述引擎具有相同的行为,如果 Chakra 没有相同的行为,我会感到惊讶。(要做得更好很快就变得非常复杂,而要做得更差,你几乎永远不会进行 GC 并且会有严重的内存问题...)