简体   繁体   English

Javascript / ECMAScript垃圾收集

[英]Javascript/ECMAScript Garbage collection

Consider the following code (you can just put this in the developer console in Chrome and check). 请考虑以下代码(您可以将它放在Chrome中的开发人员控制台中并检查)。

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. 我的猜测是技术上window仍然保持对对象的引用,因此对象保留在内存中。 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. 我在另一个基于ECMA的语言(Actionscript)中遇到过这个问题并构建了一个用于处理它的库,但有点想法Javascript会采用不同的方法。

obj is not garbage collected because the closure that you pass to setTimeout must be kept around in order to be executed. obj不是垃圾收集的,因为传递给setTimeout的闭包必须保持不变才能被执行。 And it, in turn, holds a reference to obj because it captures myRef . 反过来,它持有对obj的引用,因为它捕获了myRef

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 hacks 1 . 现在没有办法删除计时器,没有可怕的黑客1 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. 1可怕的黑客攻击:由于计时器ID只是整数,你可以从1到1000000000循环,并在每个整数上调用clearTimeout Obviously this will kill other running timers! 显然这会杀死其他正在运行的计时器!

  • List item 项目清单

Of course the timer still fires; 当然计时器仍然会发射; you're recursively calling it inside the nested function with myRef.f. 你用myRef.f在嵌套函数中递归调用它。

Your guess was that the window holds a reference to obj. 你的猜测是窗口保存了对obj的引用。 That is true, however, that's not why setTimeout is recursively called, nor what can be done to cancel it. 然而,这是正确的,这不是递归调用setTimeout的原因,也不是取消它的方法。

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. 要停止计时器,只需调用clearTimeout,然后不要递归调用setTimeout。 A basic example: 一个基本的例子:

(Identifier val is created as a property of the global object. Always use var!) (标识符val被创建为全局对象的属性。始终使用var!)

var obj = {
    f : function (i) {
        // (GS) `this` is the base of `f` (aka obj).
        var myRef = this;
        var timer = setTimeout(function () { 
            if(i == 0) {
                clearTimeout(timer);
                return;
            }
            console.log(i, "time down!"); 
            myRef.f(--i);
        }, 1000);
    }
};

obj.f(4);

Moving a step up from that, an isDone method can provide more featureful check with refs passed back an forth. 从那里向上迈出一步,isDone方法可以提供更多功能性检查,其中refs传回来。 The setTimeout can be changed to setInterval. 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);

In response to K2xL's comment. 回应K2xL的评论。

A minor adjustment of your function and it does behave like you suggest. 对您的功能进行微调,它的行为与您的建议相符。 If obj is given a new value the if will fail, the propagation will stop, and the whole lot can be garbage collected: 如果obj被赋予一个新值, if将失败,传播将停止,整个批次可以被垃圾收集:

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

I'd prefer a slightly flatter structure, you can skip the object container and rely just on a standard closure: 我更喜欢稍微扁平的结构,你可以跳过对象容器并依赖于标准的闭包:

(function(){
    var marker={}
    window.obj=marker
    function iterator(){
        if(window.obj===marker){
            setTimeout(iterator,1000)
            console.log("time down!")
        }
    }
    iterator()
})()

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: 即使具有相同id的新元素在其位置竖立,当元素从文档中移除时传播仍将停止,因为新元素不等于旧元素:

(function(){
    var marker=document.getElementById("marker")
    function iterator(){
        if(document.getElementById("marker")===marker){
            setTimeout(iterator,1000)
            console.log("time down!")
        }
    }
    iterator()
})()

The garbage collector doesn't clear out the timer function because something in the implementation of setTimeout() maintains a reference to it until you call clearTimeout() . 垃圾收集器不会清除计时器函数,因为在调用clearTimeout()之前, setTimeout()实现中的某些内容会维护对它的引用。

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). 你是正确的,如果你不清除它并删除对“setTimeout()”返回的值的引用,那么你引入了“内存泄漏”(因为无法删除定时器函数)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM