Javascript/ECMAScript垃圾收集

Javascript/ECMAScript垃圾收集,javascript,garbage-collection,settimeout,Javascript,Garbage Collection,Settimeout,考虑以下代码(您可以将其放在Chrome中的开发者控制台中并检查) 如果我跑了 obj.f(); obj = null; 要启动计时器,我可以每秒看到“时间停止!” 如果我跑了 obj.f(); obj = null; 计时器仍在启动 只是好奇为什么垃圾收集不清除计时器?可怕的是,现在似乎没有办法删除计时器了-我说的对吗 我的猜测是,从技术上讲,window仍然保存对该对象的引用,因此该对象仍保留在内存中。我曾在另一种基于ECMA的语言(Actionscript)中遇到过这个问题,并构建

考虑以下代码(您可以将其放在Chrome中的开发者控制台中并检查)

如果我跑了

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(),它可能会被垃圾回收。