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)
引用y中bar调用关闭,所以只要我保持z周围的垃圾收集器不会清理y.问题是 - 会发生什么x?即使它没有被引用,它是否也被该闭包持有?垃圾收集器是否会看到没有参考x并清理它?或者只要我抓住它就会x坚持y下去z?(理想的答案是引用ECMA规范.)
问题是 - 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可以清理的时候,没有成功(在撰写本文时).:-(
| 归档时间: |
|
| 查看次数: |
967 次 |
| 最近记录: |