Javascript/ECMAScript垃圾收集
考虑以下代码(您可以将其放在Chrome中的开发者控制台中并检查) 如果我跑了Javascript/ECMAScript垃圾收集,javascript,garbage-collection,settimeout,Javascript,Garbage Collection,Settimeout,考虑以下代码(您可以将其放在Chrome中的开发者控制台中并检查) 如果我跑了 obj.f(); obj = null; 要启动计时器,我可以每秒看到“时间停止!” 如果我跑了 obj.f(); obj = null; 计时器仍在启动 只是好奇为什么垃圾收集不清除计时器?可怕的是,现在似乎没有办法删除计时器了-我说的对吗 我的猜测是,从技术上讲,window仍然保存对该对象的引用,因此该对象仍保留在内存中。我曾在另一种基于ECMA的语言(Actionscript)中遇到过这个问题,并构建
obj.f();
obj = null;
要启动计时器,我可以每秒看到“时间停止!”
如果我跑了
obj.f();
obj = null;
计时器仍在启动
只是好奇为什么垃圾收集不清除计时器?可怕的是,现在似乎没有办法删除计时器了-我说的对吗
我的猜测是,从技术上讲,
window
仍然保存对该对象的引用,因此该对象仍保留在内存中。我曾在另一种基于ECMA的语言(Actionscript)中遇到过这个问题,并构建了一个库来处理它,但我认为Javascript会采取不同的方法。垃圾收集器没有清除计时器函数,因为setTimeout()的实现中出现了一些问题
保留对它的引用,直到调用clearTimeout()
如果不清除它并删除对“setTimeout()”返回的值的引用,则说明引入了“内存泄漏”(因为无法删除计时器函数),这是正确的.
obj
不会被垃圾收集,因为传递给setTimeout
的闭包必须保留在周围才能执行。反过来,它保存对obj
的引用,因为它捕获myRef
如果您将该闭包传递给任何其他保持该闭包的函数(例如,在数组中),则情况也是如此
如果没有可怕的黑客攻击,现在无法删除计时器1。但这是很自然的:物体的工作就是清理它自己。这个对象的目的是无限地触发一个超时,这样对象显然打算在它自己之后永远不会清理,这可能是合适的。你不能期望某件事永远发生而不消耗至少一些内存
1个可怕的黑客:因为计时器ID只是整数,所以可以从1循环到100000000,并对每个整数调用
clearTimeout
。显然,这将杀死其他运行计时器 回应K2xL的评论
对你的功能进行一个小小的调整,它的行为确实符合你的建议。如果obj
被赋予一个新值,则如果将失败,传播将停止,整个批次可以被垃圾收集:
var obj = {
f: function () {
var myRef = this;
if(myRef===obj){
val = setTimeout(function () {
console.log("time down!");
myRef.f();
}, 1000);
}
}
};
我更喜欢稍微平坦的结构,您可以跳过对象容器,只依赖标准闭包:
(function(){
var marker={}
window.obj=marker
function iterator(){
if(window.obj===marker){
setTimeout(iterator,1000)
console.log("time down!")
}
}
iterator()
})()
请注意,您可以使用您想要的任何对象作为标记,这很容易成为文档元素。即使在其位置安装了具有相同id的新元素,当该元素从文档中删除时,传播仍将停止,因为新元素不等于旧元素:
(function(){
var marker=document.getElementById("marker")
function iterator(){
if(document.getElementById("marker")===marker){
setTimeout(iterator,1000)
console.log("time down!")
}
}
iterator()
})()
- 列表项
当然,计时器仍然会启动;您正在使用myRef.f在嵌套函数中递归调用它
您的猜测是,该窗口包含对obj的引用。这是真的,但是,这不是为什么setTimeout被递归调用,也不是什么可以取消它
有几种方法可以提供计时器清除。一种方法是在开始时传入一个条件函数
要停止计时器,只需调用clearTimeout,然后不要递归调用setTimeout。一个基本的例子:
(标识符val
作为全局对象的属性创建。始终使用var!)
var obj={
f:职能(一){
//(GS)`this`是`f`(又名obj)的基。
var myRef=这个;
var timer=setTimeout(函数(){
如果(i==0){
清除超时(计时器);
返回;
}
日志(i,“时间停止!”);
myRef.f(--i);
}, 1000);
}
};
目标f(4);
再向前推进一步,isDone方法可以提供更具特色的检查,并将ref来回传递。setTimeout可以更改为setInterval
var obj = {
f : function (i, isDone, animEndHandler) {
var timer = setInterval(function() {
console.log(i, "time down!");
if(isDone(--i)) {
animEndHandler({toString: function(){return"blast off!"}, i: i});
clearInterval(timer);
}
}, 1000);
}
};
function isDone(i) {
return i == 0;
}
function animEndHandler(ev) {
console.log(""+ev);
}
obj.f(3, isDone, animEndHandler);
var obj={
f:函数(i、isDone、animEndHandler){
var timer=setInterval(函数(){
日志(i,“时间停止!”);
if(isDone(--i)){
animEndHandler({toString:function(){return“blast off!”},i:i});
清除间隔(计时器);
}
}, 1000);
}
};
功能isDone(i){
返回i==0;
}
函数animEndHandler(ev){
控制台日志(“+ev”);
}
对象f(3,isDone,animEndHandler);
也许值得一试答案:这样做的时候,似乎没有办法停止计时器!这并不是一个需要特殊处理的问题——这是设计的。如果您打算在用户离开页面之前停止计时器,那么请保存setTimeout
中的返回值,而不是将其丢弃,这样您就可以使用cleartimout
来停止计时器。在实际代码中,您不会碰巧使用类似于此的任何东西吗?内联函数声明、容器对象和从一个作用域泄漏到另一个作用域的全部组合,都使得代码很难读取。有一些情况下,一个小包装可以解决一些问题,但这似乎不是其中之一。@电子商务,当然我不会,但考虑一些使用自己的“计时器”的LIBA。然后考虑有多少其他编码器做$(“div”).html(“”)来“清除”它,而不是使用一些图表的“销毁”方法。那些计时器可能不会被摧毁。。。我希望有人能在潜水器上放一个设定计时器。。。然后,如果div被清除,计时器也被清除。。。可能会减少appsHow中的内存泄漏您知道阻止垃圾收集的是setTimeout()吗?它可能是console.log(),如果删除console.log(),它可能会被垃圾回收。