关于未引用变量的JavaScript闭包

时间:2014-06-28 16:17:32

标签: javascript memory-management garbage-collection closures ecmascript-5

我知道封​​闭herehere上的精彩帖子,但似乎都没有解决我想到的特定情况。最好用代码证明这个问题:

function foo() {
    var x = {};
    var y = "whatever";

    return function bar() {
        alert(y);
    };
}

var z = foo();

y内引用bar会调用一个闭包,只要我在垃圾收集器周围保留z就不会清除y。问题是 - x会发生什么?即使它没有被引用,它是否也被该闭包持有?垃圾收集器是否会看到没有引用x并将其清理干净?或者只要我抓住xyz一直存在吗? (理想的答案是引用ECMA规范。)

1 个答案:

答案 0 :(得分:8)

  

问题是 - x会发生什么?

答案因理论与实施而异。

理论中,是的,x保持活着,因为闭包(匿名函数)具有对{{1}调用的上下文的绑定对象的引用},包括foo

practice 中,现代JavaScript引擎非常智能。如果他们可以向自己证明x 无法从关闭中引用,他们可以将其排除在外。他们这样做的程度因发动机而异。示例:V8(Chrome和其他地方的引擎)将以xx开头,甚至<{1}}引用的对象 >堆栈,而不是堆;然后在退出y时,它会查看哪些内容仍有未完成的引用,移动到堆中。然后它弹出堆栈指针,其他东西不再存在。 : - )

那么,他们怎么能证明呢?基本上,如果闭包中的代码没有引用它并且没有使用xfoo,那么JavaScript引擎很可能知道{{1}不需要。


如果您需要确保即使eval仍然存在,该对象也可用于GC,即使在可能是字面上(愚蠢)的旧浏览器上,您也可以这样做:

new Function

这意味着什么都没有保留对用于引用的对象x的引用。因此即使x仍然存在,至少它所引用的对象已准备好收割。而且它是无害的。但同样,现代引擎会为您优化一些东西,除非您遇到特定的性能问题,否则我不会担心它,并将其跟踪到一些代码分配大型对象,一旦函数返回就不会引用它们,但似乎没有得到清理。


不幸的是,正如您在下面指出的那样,这是有限制的,例如this question中提到的那个。但它并不是所有的悲观和沮丧,请参阅下面的个人资料快照,了解你可以做些什么...

让我们使用Chrome的堆快照功能在V8中查看此代码:

x = undefined;

这是扩展堆快照:

no description available

在处理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; 时,没有成功(在撰写本文时)。 : - (