确实存在阻止您调用静态分析会遗漏的 eval 的限制是不正确的:只是对 eval 的此类引用在全局范围内运行。请注意,这是 ES5 中 ES3 的一个变化,其中对 eval 的间接和直接引用都在本地范围内运行,因此,我不确定是否真的有任何基于此事实的优化。
一个明显的测试方法是让 BigObject 成为一个真正的大对象,并在运行 f0-f2 后强制执行 gc。(因为,嘿,尽管我认为我知道答案,但测试总是更好的!)
所以…
考试
var closure;
function BigObject() {
var a = '';
for (var i = 0; i <= 0xFFFF; i++) a += String.fromCharCode(i);
return new String(a); // Turn this into an actual object
}
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; })"); // direct eval
}
function f4() {
var x = new BigObject();
var y = 0;
closure = (1,eval)("(function(){ return 7; })"); // indirect eval (evaluates in global scope)
}
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;"); // creates function in global scope
}
我已经为 eval/Function 添加了测试,看起来这些也是有趣的案例。f5/f6 之间的不同很有趣,因为 f5 实际上与 f3 完全相同,因为闭包函数实际上是相同的;f6 只是返回一些一旦评估就给出的东西,并且由于尚未评估 eval,编译器无法知道其中没有对 x 的引用。
蜘蛛猴
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 之外的所有内容上都出现了 GC“x”。
除非在任何仍然存在的函数的作用域链内有直接的 eval 调用,否则它看起来尽可能多(即,如果可能,y 和 x 一样)。(即使该函数对象本身已被 GC 处理并且不再存在,就像 f5 中的情况一样,这在理论上意味着它可以 GC 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 x 的所有内容都显示为 V8。这与 SpiderMonkey 相同,请参见上面的分析。(但是请注意,这些数字不够详细,无法判断 y 是否被 GC 处理,而 x 不是,我没有费心去调查。)
卡拉坎
我不打算再次运行它,但不用说,行为与 SpiderMonkey 和 V8 相同。没有 JS shell 很难测试,但随着时间的推移是可行的。
JSC(硝基)和查克拉
在 Linux 上构建 JSC 很痛苦,而 Chakra 不能在 Linux 上运行。我相信 JSC 与上述引擎具有相同的行为,如果 Chakra 也没有,我会感到惊讶。(做任何更好的事情很快变得非常复杂,做任何更糟的事情,好吧,你几乎永远不会做 GC 并且有严重的内存问题......)