【问题标题】:Javascript/ECMAScript Garbage collectionJavascript/ECMAScript 垃圾收集
【发布时间】:2012-02-09 04:07:03
【问题描述】:

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

var obj = {
    f: function () {
        var myRef = this;
        val = setTimeout(function () { 
            console.log("time down!"); 
            myRef.f();
        }, 1000);
    }
};

如果我再跑

obj.f();

要启动计时器,我可以看到每一秒“时间到了!”

如果我再跑

obj = null;

计时器仍在触发。

只是好奇为什么垃圾回收不清除计时器?可怕的是,似乎现在无法删除计时器 - 我说的对吗?

我的猜测是,从技术上讲,window 仍然持有对该对象的引用,因此该对象仍保留在内存中。我在另一种基于 ECMA 的语言(Actionscript)中遇到过这个问题,并构建了一个库来处理它,但我认为 Javascript 会采用不同的方法。

【问题讨论】:

  • 可能值得查看以下答案:stackoverflow.com/questions/858619/… 似乎没有办法像这样完成计时器!
  • 这并不是一个需要特殊处理的问题——这是设计使然。如果您打算在用户离开页面之前停止计时器,请保存来自setTimeout 的返回值而不是将其丢弃,这样您就可以使用clearTimeout 来停止它。
  • 你不会碰巧在实际代码中使用任何看起来像这样的东西吗?内联函数声明、容器对象以及从一个作用域泄漏到另一个作用域的整个组合,这一切都使得代码很难阅读。在某些情况下,稍微打包可以解决一些问题,但这似乎不是其中之一。
  • @eBusiness 当然我不会,但是考虑一些使用自己的“计时器”的库......然后考虑有多少其他编码人员在做 $("div").html("")将其“清除”而不是使用某些图表的“销毁”方法。那些计时器可能不会被破坏......我希望可以将 setTimer 放在 div 上......然后如果 div 被清除,计时器也会被清除......可能会导致应用程序中的内存泄漏更少

标签: javascript garbage-collection settimeout


【解决方案1】:

obj 不会被垃圾回收,因为传递给setTimeout 的闭包必须保留才能执行。而它又持有对obj 的引用,因为它捕获了myRef

如果您将该闭包传递给任何其他保留它的函数(例如在数组中),情况也是如此。

现在没有办法删除计时器,没有可怕的黑客攻击1。但这是很自然的:清理自己是一个对象的工作。该对象的目的是无限触发超时,因此该对象显然打算永远不清理自己,这可能是合适的。你不能指望某件事会永远发生而不用至少一些内存。


1 可怕的 hack:由于计时器 ID 只是整数,因此您可以从 1 到 1000000000 循环并在每个整数上调用 clearTimeout。显然这会杀死其他正在运行的计时器!

【讨论】:

  • 你怎么知道是 setTimeout() 阻止了垃圾回收?它可能是 console.log(),如果你删除 console.log(),它可能会被垃圾收集。
【解决方案2】:

回应 K2xL 的评论。

对您的功能稍作调整,它的行为确实像您建议的那样。如果obj 被赋予一个新值,if 将失败,传播将停止,整个批次可以被垃圾收集:

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()
})()

【讨论】:

    【解决方案3】:
    • 列表项

    当然,计时器仍然会触发;您正在使用 myRef.f 在嵌套函数中递归调用它。

    您的猜测是该窗口包含对 obj 的引用。确实如此,但这不是递归调用 setTimeout 的原因,也不是可以取消它的原因。

    有几种方法可以清除定时器。一种方法是在开始时传入条件函数。

    要停止计时器,只需调用 clearTimeout,然后不要递归调用 setTimeout。一个基本的例子:

    (标识符val被创建为全局对象的属性。始终使用var!)

    变量 obj = { f : 函数 (i) { // (GS) `this` 是 `f` (aka obj) 的基础。 var myRef = 这个; var timer = setTimeout(function () { 如果(我 == 0){ 清除超时(定时器); 返回; } console.log(i, "时间到了!"); myRef.f(--i); }, 1000); } }; obj.f(4);

    更进一步,isDone 方法可以通过来回传递的 ref 提供更多功能检查。 setTimeout 可以改为 setInterval。

    变量 obj = { f : 函数 (i, isDone, animEndHandler) { var timer = setInterval(function() { console.log(i, "时间到了!"); 如果(完成(--i)){ animEndHandler({toString: function(){return"发射!"}, i: i}); 清除间隔(定时器); } }, 1000); } }; 功能完成(一){ 返回 i == 0; } 函数 animEndHandler(ev) { 控制台.log(""+ev); } obj.f(3, isDone, animEndHandler);

    【讨论】:

      【解决方案4】:

      垃圾收集器不会清除计时器函数,因为在 setTimeout() 的实现中的某些东西会保持对它的引用,直到您调用 clearTimeout()

      您是正确的,如果您不清除它并删除对“setTimeout()”返回值的引用,那么您已经引入了“内存泄漏”(因为无法删除计时器功能)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-12-28
        • 2011-01-21
        • 1970-01-01
        • 2018-12-30
        • 1970-01-01
        • 1970-01-01
        • 2021-10-18
        相关资源
        最近更新 更多