关于未引用变量的JavaScript闭包 [英] JavaScript Closures Concerning Unreferenced Variables

查看:158
本文介绍了关于未引用变量的JavaScript闭包的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道Closures的精彩帖子这里这里,但似乎都不在以处理我所想的特殊情况。问题最好用代码演示:

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

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

var z = foo();

y > bar 调用一个闭包,只要我保持 z 周围的垃圾回收器不会清除 / code>。问题是 - x 会发生什么?它是由闭包持有,即使它没有被引用?垃圾收集器会看到没有引用 x 并清理吗?或者将 x y 一起保存,只要保持 z ? (一个理想的答案会引用ECMA规范。)

解决方案


问题是 - 到x?


答案因理论与实施而异。



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



实践中,现代的JavaScript引擎是相当聪明的。如果他们能够证明 x 不能从闭包引用,他们可以离开它。他们这样做的程度因发动机而异。示例:V8(Chrome和其他地方的引擎)将以 x y 开始,甚至 object x 是指堆栈,而不是堆;那么当退出 foo 时,它会查看哪些内容仍然有未完成的引用,并且将它们移动到堆。然后它弹出堆栈指针,其他的东西不再存在了。 : - )



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






如果你需要确保即使 x 仍然存在,对象也可以用于GC,即使在旧的浏览器上也可能是字面的它可以这样做:

  x = undefined; 

这意味着没有任何内容可以引用对象 x 用于指。因此,即使 x 仍然存在,至少它引用的对象已准备好获取。它是无害的。但同样,现代引擎将为你优化的东西,我不会担心它,除非你面临一个特定的性能问题,跟踪到一些代码分配大的对象,一旦函数返回时没有引用,






不幸的是,正如您在下面指出的,这有一些限制,例如在此问题中提及的。但是它并不是所有的厄运和阴暗,见下面的配置文件快照你可以做...



让我们看看这个代码在V8,使用Chrome的堆快照功能:

  function UsedFlagClass_NoFunction(){} 
function UnusedFlagClass_NoFunction(){}
function build_NoFunction b $ b 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 函数,V8成功标识出从未使用引用的对象无法到达,并且摆脱它,但它不尽管事实上无法达到 unreachable ,因此无法达到未使用通过它。



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



任何可以通过静态分析处理的东西,我们可以在它上面引用一个JavaScript到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 b $ b 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();



如你所见,静态分析告诉CC 无法访问是死代码,因此它完全删除。



但是当然,你可能 在函数过程中的某些东西,并且只是不需要它在函数完成后。它不是死代码,但它是代码,你不需要的时候函数结束。在这种情况下,您必须求助于:

  unused = undefined; 

因为你不再需要该函数,你可能还会释放它:

  unused = unreachable = undefined; 

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

不,可惜,只是:

  unreachable = undefined; 

...在V8中没有成功c $ c>未使用可以清理。 : - (


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. The question is -- what happens to 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? Or will x persists along with y as long as I hold onto z? (An ideal answer would cite the ECMA Specification.)

解决方案

The question is -- what happens to 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.

In practice, modern JavaScript engines are quite smart. If they can prove to themselves that x cannot be referenced from the closure, they can leave it out. 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; then when exiting foo, it looks to see what things still have outstanding references, and moves those to the heap. 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.


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 = undefined;

That means nothing keeps a reference to the object x used to refer to. So even though x still exists, at least the object it referred to is ready for reaping. 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:

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.

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. Even in "simple" mode, the beautified result of "compiling" the code above with Closure Compiler looks like this:

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.

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. 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. :-(

这篇关于关于未引用变量的JavaScript闭包的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆