Это неправда, что существуют ограничения, которые не позволяют вам вызывать eval, который был бы пропущен при статическом анализе: просто такие ссылки на eval выполняются в глобальной области видимости. Обратите внимание, что это изменение в ES5 по сравнению с ES3, когда косвенные и прямые ссылки на eval оба выполнялись в локальной области, и поэтому я не уверен, что что-то действительно делает какие-либо оптимизации, основанные на этом факте.
Очевидный способ проверить это - сделать BigObject действительно большим объектом и принудительно вызвать gc после запуска f0 – f2. (Потому что, насколько я думаю, я знаю ответ, тестирование всегда лучше!)
Итак ...
Тест
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.
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 отображается для GC "x" на всем, кроме f3, f5 и f6.
Это выглядит как можно больше (то есть, когда это возможно, y, а также x), если в цепочке областей действия какой-либо функции, которая все еще существует, нет прямого вызова eval. (Даже если сам объект функции был GC'd и больше не существует, как в случае с 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.
V8 отображается для GC x на всем, кроме f3, f5 и f6. Это идентично SpiderMonkey, см. Анализ выше. (Тем не менее, обратите внимание, что цифры не достаточно подробны, чтобы сказать, является ли y GC'd, когда x нет, я не потрудился исследовать это.)
Carakan
Я не собираюсь снова запускать это, но само собой разумеется, что поведение идентично SpiderMonkey и V8. Сложнее тестировать без оболочки JS, но выполнимо со временем.
АО (Нитро) и Чакра
Создание JSC - это боль в Linux, а Chakra не работает в Linux. Я полагаю, что АО имеет то же самое поведение с вышеупомянутыми двигателями, и я был бы удивлен, если бы у Чакры тоже не было. (Делать что-то лучше быстро становится очень сложно, делать что-то хуже, ну, вы почти никогда не будете делать ГХ и у вас будут серьезные проблемы с памятью…)