关于未引用变量的JavaScript闭包

use*_*275 5 javascript closures garbage-collection memory-management ecmascript-5

我知道这里这里关于闭包的好帖子,但似乎都没有解决我想到的特殊情况.最好用代码证明这个问题:

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

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

var z = foo();
Run Code Online (Sandbox Code Playgroud)

引用ybar调用关闭,所以只要我保持z周围的垃圾收集器不会清理y.问题是 - 会发生什么x?即使它没有被引用,它是否也被该闭包持有?垃圾收集器是否会看到没有参考x并清理它?或者只要我抓住它就会x坚持y下去z?(理想的答案是引用ECMA规范.)

T.J*_*der 8

问题是 - x会发生什么?

答案因理论与实施而异.

理论上,是的,x被保持活着的,因为封闭件(匿名函数)必须调用的上下文中的结合对象的引用foo,其包括x.

实践中,现代JavaScript引擎非常聪明.如果他们可以向自己证明x 无法从封闭中引用,他们可以将其排除在外.他们这样做的程度因发动机而异.例如:V8(在浏览器和其它地方的引擎)将在开始时x,y以及甚至在对象x是指上,而不是堆; 然后在退出时foo,它会查看哪些内容仍有未完成的引用,并将它们移动到堆中.然后它弹出堆栈指针,其他东西不再存在.:-)

那么,他们怎么能证明呢?基本上,如果闭包中的代码没有引用它并且不使用evalor new Function,则JavaScript引擎很可能知道x不需要.


如果您需要确保,即使x仍然存在,对象是供GC甚至关于它的旧的浏览器,可能是文字(哑),你可以这样做:

x = undefined;
Run Code Online (Sandbox Code Playgroud)

这意味着没有任何东西保留对x用于引用的对象的引用.所以即使它x仍然存在,至少它所提到的对象已准备好收割.而且它是无害的.但同样,现代引擎会为你优化一些东西,我不担心它,除非你遇到一个特定的性能问题,并追踪到一些代码分配一旦函数返回后没有引用的大对象,但是不要似乎要清理干净了.


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

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

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();
Run Code Online (Sandbox Code Playgroud)

这是扩展堆快照:

没有可用的描述

当处理build_NoFunction功能,V8成功地识别从引用的对象notused不能达到并摆脱它,但它并没有在任何的其他情况下这样做,尽管事实上unreachable无法达成,因此notused无法通过达成它.

那么我们可以做些什么来避免这种不必要的内存消耗呢?

好吧,对于任何可以通过静态分析处理的东西,我们可以在它上面放一个JavaScript-to-JavaScript编译器,比如Google的Closure Compiler.即使在"简单"模式下,使用Closure Compiler"编译"上面代码的美化结果如下所示:

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();
Run Code Online (Sandbox Code Playgroud)

如你所见,静态分析告诉CC这unreachable是死代码,所以它完全删除了它.

但是当然,你可能在函数的过程中使用 unreachable了某些东西,并且在函数完成后就不需要了.它不是死代码,但它函数结束时不需要的代码.在这种情况下,你必须诉诸:

unused = undefined;
Run Code Online (Sandbox Code Playgroud)

在末尾.由于您不再需要该功能,您可能还会发布它:

unused = unreachable = undefined;
Run Code Online (Sandbox Code Playgroud)

(是的,你可以这样做,即使它是用函数声明创建的.)

不,遗憾的是,只是这样做:

unreachable = undefined;
Run Code Online (Sandbox Code Playgroud)

......在使V8弄清楚unused可以清理的时候,没有成功(在撰写本文时).:-(