简体   繁体   English

关于未引用变量的JavaScript闭包

[英]JavaScript Closures Concerning Unreferenced Variables

I'm aware of the great posts on Closures here and here , but neither seems to address the particular case I have in mind. 我知道这里这里关于闭包的好帖子,但似乎都没有解决我想到的特殊情况。 The question is best demonstrated with code: 最好用代码证明这个问题:

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

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

var z = foo();

Referencing y within bar invokes a closure, and so long as I keep z around the garbage collector won't clean up y . bar引用y调用一个闭包,只要我在垃圾收集器周围保持z就不会清理y The question is -- what happens to x ? 问题是 - x会发生什么? Is it held by that closure too even though it doesn't get referenced? 即使它没有被引用,它是否也被该闭包持有? Will the garbage collector see there's no reference x and clean it up? 垃圾收集器是否会看到没有参考x并清理它? Or will x persists along with y as long as I hold onto z ? 或将x随着持续y只要我守住z (An ideal answer would cite the ECMA Specification.) (理想的答案是引用ECMA规范。)

The question is -- what happens to x? 问题是 - x会发生什么?

The answer varies depending on theory vs. implementation. 答案因理论与实施而异。

In theory , yes, x is kept alive, because the closure (the anonymous function) has a reference to the binding object of the context of the call to foo , which includes x . 理论上 ,是的, x保持活着,因为闭包(匿名函数)具有对foo调用的上下文的绑定对象的引用,其中包括x

In practice , modern JavaScript engines are quite smart. 实践中 ,现代JavaScript引擎非常聪明。 If they can prove to themselves that x cannot be referenced from the closure, they can leave it out. 如果他们能够向自己证明x 不能从闭包中引用,他们可以将其遗漏。 The degree to which they do that will vary from engine to engine. 他们这样做的程度因发动机而异。 Example: V8 (the engine in Chrome and elsewhere) will start out with x , y , and even the object that x refers to on the stack , not the heap; 示例:V8(Chrome和其他地方的引擎)将以xy开头,甚至是x堆栈上引用的对象 ,而不是堆; then when exiting foo , it looks to see what things still have outstanding references, and moves those to the heap. 然后在退出foo ,它会查看哪些内容仍有未完成的引用,并将它们移到堆中。 Then it pops the stack pointer, and the other things don't exist anymore. 然后它弹出堆栈指针,其他东西不再存在。 :-) :-)

So, how can they prove it? 那么,他们怎么能证明呢? Basically, if the code in the closure doesn't refer to it and doesn't use eval or new Function , the JavaScript engine is likely to be able to know that x isn't needed. 基本上,如果闭包中的代码没有引用它并且不使用evalnew Function ,则JavaScript引擎很可能知道不需要x


If you need to be sure that even if x still exists, the object is available for GC even on older browsers that might be literal (dumb) about it, you can do this: 如果您需要确保即使x仍然存在,该对象也可用于GC,即使在可能是文字(哑)的旧浏览器上,您也可以这样做:

x = undefined;

That means nothing keeps a reference to the object x used to refer to. 这意味着什么都没有保留对用于引用的对象x引用。 So even though x still exists, at least the object it referred to is ready for reaping. 因此即使x仍然存在,至少它所提到的对象已准备好收割。 And it's harmless. 而且它是无害的。 But again, modern engines will optimize things for you, I wouldn't worry about it unless you were faced with a specific performance problem and tracked it down to some code allocating large objects that aren't referenced once the function returns, but don't seem to be getting cleaned up. 但同样,现代引擎会为你优化一些东西,我不担心它,除非你遇到一个特定的性能问题,并将其跟踪到一些代码,分配一旦函数返回时未引用的大对象,但不要似乎要清理干净了。


Unfortunately, as you pointed out below, there are limits to this, such as the one mentioned in this question . 不幸的是,正如您在下面指出的那样,这方面存在限制,例如本问题中提到的那个。 But it's not all doom and gloom, see below under the profile snapshot for what you can do... 但这并不是所有的悲观和沮丧,请参阅下面的个人资料快照,了解你可以做些什么......

Let's look this code in V8, using Chrome's heap snapshot feature: 让我们使用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();

And here's the expanded heap snapshot: 这是扩展堆快照:

没有可用的描述

When processing the build_NoFunction function, V8 successfully identifies that the object referenced from notused cannot be reached and gets rid of it, but it doesn't do so in either of the other scenarios, despite the fact that unreachable cannot be reached, and therefore notused cannot be reached through it. 当处理build_NoFunction功能,V8成功地识别从引用的对象notused不能达到并摆脱它,但它并没有在任何的其他情况下,这样做尽管unreachable无法达成,因此notused无法通过它达成。

So what can we do to avoid this kind of unnecessary memory consumption? 那么我们可以做些什么来避免这种不必要的内存消耗呢?

Well, for anything that can be handled via static analysis, we can throw a JavaScript-to-JavaScript compiler at it, like Google's Closure Compiler. 好吧,对于任何可以通过静态分析处理的东西,我们可以在它上面放一个JavaScript-to-JavaScript编译器,比如Google的Closure Compiler。 Even in "simple" mode, the beautified result of "compiling" the code above with Closure Compiler looks like this: 即使在“简单”模式下,使用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();

As you can see, static analysis told CC that unreachable was dead code, and so it removed it entirely. 正如您所看到的,静态分析告诉CC, unreachable是死代码,因此它完全删除了它。

But of course, you probably used unreachable for something during the course of the function, and just don't need it after the function completes. 但是,当然,你可能在函数过程中使用了 unreachable的东西,并且在函数完成后就不需要它了。 It's not dead code, but it is code you don't need when the function ends. 它不是死代码,但它函数结束时不需要的代码。 In that case, you have to resort to: 在这种情况下,你必须诉诸:

unused = undefined;

at the end. 在末尾。 Since you don't need the function anymore, you might also release it: 由于您不再需要该功能,您可能还会发布它:

unused = unreachable = undefined;

(Yes, you can do that, even when it was created with a function declaration.) (是的,你可以这样做,即使它是用函数声明创建的。)

And no, sadly, just doing: 不,遗憾的是,只是这样做:

unreachable = undefined;

...doesn't succeed (as of this writing) in making V8 figure out that unused can be cleaned up. ...在使V8弄清楚unused可以被清理时,没有成功(在撰写本文时)。 :-( :-(

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM