我知道封闭here和here上的精彩帖子,但似乎都没有解决我想到的特定情况。最好用代码证明这个问题:
function foo() {
var x = {};
var y = "whatever";
return function bar() {
alert(y);
};
}
var z = foo();
在y
内引用bar
会调用一个闭包,只要我在垃圾收集器周围保留z
就不会清除y
。问题是 - x
会发生什么?即使它没有被引用,它是否也被该闭包持有?垃圾收集器是否会看到没有引用x
并将其清理干净?或者只要我抓住x
,y
会z
一直存在吗? (理想的答案是引用ECMA规范。)
答案 0 :(得分:8)
问题是 - x会发生什么?
答案因理论与实施而异。
在理论中,是的,x
保持活着,因为闭包(匿名函数)具有对{{1}调用的上下文的绑定对象的引用},包括foo
。
在 practice 中,现代JavaScript引擎非常智能。如果他们可以向自己证明x
无法从关闭中引用,他们可以将其排除在外。他们这样做的程度因发动机而异。示例:V8(Chrome和其他地方的引擎)将以x
,x
开头,甚至<{1}}引用的对象 >堆栈,而不是堆;然后在退出y
时,它会查看哪些内容仍有未完成的引用,将移动到堆中。然后它弹出堆栈指针,其他东西不再存在。 : - )
那么,他们怎么能证明呢?基本上,如果闭包中的代码没有引用它并且没有使用x
或foo
,那么JavaScript引擎很可能知道{{1}不需要。
如果您需要确保即使eval
仍然存在,该对象也可用于GC,即使在可能是字面上(愚蠢)的旧浏览器上,您也可以这样做:
new Function
这意味着什么都没有保留对用于引用的对象x
的引用。因此即使x
仍然存在,至少它所引用的对象已准备好收割。而且它是无害的。但同样,现代引擎会为您优化一些东西,除非您遇到特定的性能问题,否则我不会担心它,并将其跟踪到一些代码分配大型对象,一旦函数返回就不会引用它们,但似乎没有得到清理。
不幸的是,正如您在下面指出的那样,这是有限制的,例如this question中提到的那个。但它并不是所有的悲观和沮丧,请参阅下面的个人资料快照,了解你可以做些什么...
让我们使用Chrome的堆快照功能在V8中查看此代码:
x = undefined;
这是扩展堆快照:
在处理x
函数时,V8成功识别出无法访问x
引用的对象并将其删除,但在其他任何一种情况下都不会这样做,尽管无法联系到function UsedFlagClass_NoFunction() {}
function UnusedFlagClass_NoFunction() {}
function build_NoFunction() {
var notused = new UnusedFlagClass_NoFunction();
var used = new UsedFlagClass_NoFunction();
return function() { return used; };
}
function UsedFlagClass_FuncDecl() {}
function UnusedFlagClass_FuncDecl() {}
function build_FuncDecl() {
var notused = new UnusedFlagClass_FuncDecl();
var used = new UsedFlagClass_FuncDecl();
function unreachable() { notused; }
return function() { return used; };
}
function UsedFlagClass_FuncExpr() {}
function UnusedFlagClass_FuncExpr() {}
function build_FuncExpr() {
var notused = new UnusedFlagClass_FuncExpr();
var used = new UsedFlagClass_FuncExpr();
var unreachable = function() { notused; };
return function() { return used; };
}
window.noFunction = build_NoFunction();
window.funcDecl = build_FuncDecl();
window.funcExpr = build_FuncExpr();
,因此无法通过它来build_NoFunction
。
那么我们可以做些什么来避免这种不必要的内存消耗呢?
好吧,对于任何可以通过静态分析处理的东西,我们都可以在其上抛出一个JavaScript-to-JavaScript编译器,就像Google的Closure Compiler一样。即使在&#34;简单&#34;模式,&#34;编译&#34;的美化结果Closure Compiler上面的代码如下所示:
notused
正如您所看到的,静态分析告诉CC unreachable
是死代码,因此它完全删除了它。
但是,当然,你可能在功能过程中使用 notused
来做某事,而在功能完成后就不需要它了。它不是死代码,但是代码,当函数结束时你不需要它。在这种情况下,你必须诉诸:
function UsedFlagClass_NoFunction() {}
function UnusedFlagClass_NoFunction() {}
function build_NoFunction() {
new UnusedFlagClass_NoFunction;
var a = new UsedFlagClass_NoFunction;
return function () {
return a
}
}
function UsedFlagClass_FuncDecl() {}
function UnusedFlagClass_FuncDecl() {}
function build_FuncDecl() {
new UnusedFlagClass_FuncDecl;
var a = new UsedFlagClass_FuncDecl;
return function () {
return a
}
}
function UsedFlagClass_FuncExpr() {}
function UnusedFlagClass_FuncExpr() {}
function build_FuncExpr() {
new UnusedFlagClass_FuncExpr;
var a = new UsedFlagClass_FuncExpr;
return function () {
return a
}
}
window.noFunction = build_NoFunction();
window.funcDecl = build_FuncDecl();
window.funcExpr = build_FuncExpr();
最后。由于您不再需要该功能,您也可以将其发布:
unreachable
(是的,你可以这样做,即使它是用函数声明创建的。)
不,不幸的是,只是这样做:
unreachable
...在使V8弄清楚可以清除unused = undefined;
时,没有成功(在撰写本文时)。 : - (