Javascript/ECMAScript 垃圾收集
考虑以下代码(您可以将其放入 Chrome 的开发者控制台并检查)。
var obj = {
f: function () {
var myRef = this;
val = setTimeout(function () {
console.log("time down!");
myRef.f();
}, 1000);
}
};
如果我然后跑去
obj.f();
启动计时器,我可以看到每秒“时间到了!”
如果我然后运行
obj = null;
计时器仍然会触发。
只是好奇为什么垃圾收集不清除计时器?可怕的是,似乎现在无法删除计时器 - 我是对的吗?
我的猜测是,从技术上讲,窗口仍然保留对该对象的引用,因此该对象仍保留在内存中。我在另一种基于 ECMA 的语言(Actionscript)中遇到过这个问题,并构建了一个库来处理它,但我认为 Javascript 会采取不同的方法。
Consider the following code (you can just put this in the developer console in Chrome and check).
var obj = {
f: function () {
var myRef = this;
val = setTimeout(function () {
console.log("time down!");
myRef.f();
}, 1000);
}
};
If I then run
obj.f();
to start the timer, I can see every second "time down!"
If I then run
obj = null;
The timer still fires.
Just curious why doesn't garbage collection clear out the timer? The scary thing is that it appears that there is no way to delete the timer now - am I correct?
My guess is that technically window
still holds a reference to the object still consequently the object stays in memory. I've experienced this problem in another ECMA based language (Actionscript) and built a library for handling it, but sort of thought Javascript would take a different approach.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
obj
不会被垃圾回收,因为传递给setTimeout
的闭包必须保留下来才能执行。反过来,它保存对obj
的引用,因为它捕获myRef
。如果您将该闭包传递给任何其他保留它的函数(例如在数组中),情况会是相同的。
现在没有办法删除计时器,除非有可怕的黑客攻击1。但这是很自然的:对象的工作就是清理自身。该对象的目的是无限地触发超时,因此该对象显然打算永远不会在自身之后进行清理,这可能是合适的。您不能指望某件事永远发生而不消耗至少一些内存。
1 可怕的 hack:由于计时器 ID 只是整数,因此您可以从 1 到 1000000000 循环,并对每个整数调用
clearTimeout
。显然这会杀死其他正在运行的计时器!obj
is not garbage collected because the closure that you pass tosetTimeout
must be kept around in order to be executed. And it, in turn, holds a reference toobj
because it capturesmyRef
.It would be the same if you passed that closure to any other function that kept it around (for example in an array).
There is no way to delete the timer now, without horrible hacks1. But this is pretty natural: it's an object's job to clean up after itself. This object's purpose is to infinitely fire a timeout, so that object clearly intends to never clean up after itself, which might be appropriate. You can't expect something to happen forever without using up at least some memory while it does so.
1 Horrible hack: since timer IDs are just integers, you can loop from, say, 1 to 1000000000 and call
clearTimeout
on each integer. Obviously this will kill other running timers!回应K2xL的评论。
对您的功能进行细微调整,它的行为确实如您所建议的那样。如果
obj
被赋予一个新值,则if
将失败,传播将停止,并且整个过程都可以被垃圾收集:我更喜欢稍微扁平的结构,你可以跳过对象容器并仅依赖于标准闭包:
请注意,您可以使用您想要的任何对象作为标记,这很容易成为文档元素。即使在其位置建立了具有相同 id 的新元素,当该元素从文档中删除时,传播仍然会停止,因为新元素不等于旧元素:
In response to K2xL's comment.
A minor adjustment of your function and it does behave like you suggest. If
obj
is given a new value theif
will fail, the propagation will stop, and the whole lot can be garbage collected:I'd prefer a slightly flatter structure, you can skip the object container and rely just on a standard closure:
Note that you can use any object you desire for marker, this could easily be a document element. Even if a new element with the same id is erected in its place the propagation will still stop when the element is removed from the document as the new element is not equal to the old one:
当然,计时器仍然会触发;您使用 myRef.f 在嵌套函数内递归调用它。
您的猜测是该窗口包含对 obj 的引用。确实如此,但是,这不是递归调用 setTimeout 的原因,也不是可以取消它的原因。
有几种方法可以提供定时器清除。一种方法是在一开始就传入一个条件函数。
要停止计时器,只需调用clearTimeout,然后不要递归调用setTimeout。一个基本示例:(
标识符
val
作为全局对象的属性创建。始终使用 var!)在此之上,isDone 方法可以通过来回传递的引用提供更具功能性的检查。 setTimeout可以改为setInterval。
Of course the timer still fires; you're recursively calling it inside the nested function with myRef.f.
Your guess was that the window holds a reference to obj. That is true, however, that's not why setTimeout is recursively called, nor what can be done to cancel it.
There are a few ways to provide for timer clearing. One way would be to pass in a condition function in the beginning.
To stop the timer, merely call clearTimeout and then don't recursively call setTimeout. A basic example:
(Identifier
val
is created as a property of the global object. Always use var!)Moving a step up from that, an isDone method can provide more featureful check with refs passed back an forth. The setTimeout can be changed to setInterval.
垃圾收集器不会清除计时器函数,因为
setTimeout()
实现中的某些内容会维护对它的引用,直到您调用clearTimeout()
。您是正确的,如果您不清除它并删除对“setTimeout()”返回值的引用,那么您就引入了“内存泄漏”(因为计时器函数无法删除)。
The garbage collector doesn't clear out the timer function because something in the implementation of
setTimeout()
maintains a reference to it until you callclearTimeout()
.You are correct that if you do not clear it and drop the reference to the value returned by "setTimeout()" then you have introduced a "memory leak" (in that the timer function cannot be removed).