JavaScript对象没有被破坏?

时间:2012-05-18 09:08:36

标签: javascript jquery html garbage-collection

在JavaScript方面,我是初学者,但我最近遇到了一个问题,我可以用一个小例子重现:

function TimerTest(panelId)
{
    this.PanelID = panelId;
}

TimerTest.prototype.Start = function()
{
    var self = this;

    $(document.getElementById(this.PanelID)).everyTime("1s", "Test", function(){
        self.Print();
    });
}

TimerTest.prototype.Print = function()
{
    $("#debug").append("<div>triggered...</div>");
}

function Remove()
{
    $("#main").empty();
    $("#main").html("I'm done!");
}

$(document).ready(function(){
    var timer = new TimerTest("timerpanel");
    timer.Start();
});

Run in JSFiddle

调用Remove()后,对象似乎仍然存在并打印“触发...”:(

我使用HTML5的历史记录功能通过AJAX加载内容。我基本上经历的是,我有一个带有标记的嵌入式谷歌地图和一个计时器,可以偶尔更新这些标记。当离开该页面时,我仍然可以在FireBug AJAX调用中看到更新标记。对我来说,即使从DOM中删除了面板,对象也没有被破坏?关于empty()的JQuery文档虽然说它删除了所有DOM元素及其数据。

我有点困惑。我知道在导航之前我可以在对象上调用“stopTime()”但这不是我的问题。示例中的计时器就在那里,所以我实际上可以看到TimerTest对象仍然存在(即我的Google Map仍然存在!)。

2 个答案:

答案 0 :(得分:1)

即使您要从DOM中删除#main元素,TimerTest仍会保留对它的引用。元素可以存在,但不能存在于DOM中(例如,参见detach())。

当没有对它的引用时,垃圾收集器只回收对象。

jQuery的empty()方法将从DOM中删除该元素,并将删除jQuery存储的有关通过data()方法添加的元素的任何数据,以及作为事件处理程序。

答案 1 :(得分:1)

Chrome试图运行你的小提琴,抱怨jquery.offput.ca,它称之为“已知分发恶意软件”的网站。如果我去那个jQuery插件的演示,我会得到同样的警告。因此,我无法使用该实际代码。 在您的更新中,我可以使用您的实际代码,因此请使用原始代码查看此答案的最后部分以获得答案。不过,我会留下非计时器插件的答案。

元素可能会保留在内存中,也可能不会保留在内存中,它完全取决于计时器插件的工作方式。但重要的是,无论元素是否在内存中,没有什么能阻止计时器。你需要代码才能做到这一点。

我从来没有使用过那个定时器插件,但可能它有一些方法可以告诉定时器停止,可能是通过你在你附加定时器的元素的jQuery包装器上调用。因此,在Remove函数中,您需要一种方法来识别带有计时器的元素,以便您可以停止它们。

这是一个使用而不是定时器插件的示例,以演示这些概念。这是我如何做到这一点:

  1. 当我创建setInterval计时器时,我记得名为data-timer-handle的属性上的计时器句柄。您可能需要也可能不需要,但是您需要在元素上添加某种标记,这样您就可以告诉计时器插件停止。

  2. Remove中,在清除#main之前,我会找到具有data-timer-handle属性的所有子项,并清除间隔计时器。

  3. Live copy | source

    HTML:

    <div id="main">
        <div id="timerpanel">I'm the timer panel</div>
    </div>
    <input type="button" id="removeButton" value="Remove It">
    <div id="debug"></div>
    

    JavaScript的:

    function TimerTest(panelId)
    {
        this.PanelID = panelId;
    }
    
    TimerTest.prototype.Start = function()
    {
        var self = this;
    
        // Start the timer
        var handle = setInterval(function() {
            self.Print();
        }, 1000);
    
        // Remember the handle on a data-timer-handle property
        // on the element.
        $(document.getElementById(this.PanelID)).attr("data-timer-handle", handle);
    }
    
    TimerTest.prototype.Print = function()
    {
        $("#debug").append("<div>triggered...</div>");
    }
    
    function Remove()
    {
        var main = $("#main");
        // Find any elements with timers on them, stop them
        main.find("[data-timer-handle]").each(function() {
            clearInterval($(this).attr("data-timer-handle"));
        });
        main.html("I'm done!");
    }
    
    $(document).ready(function(){
        var timer = new TimerTest("timerpanel");
        timer.Start();
        $("#removeButton").click(function() {
            Remove();
        });
    });
    

    如果我正在编写一个将计时器添加到元素的计时器插件,我肯定会在元素(data-*属性,类,等等)中添加一些内容,以便您再次找到它们。但是,我还提供了一个选项,用于在元素从DOM中删除时自动取消定时器(因此您不需要查找和停止它们),通过让每个计时器勾选检查元素是否仍然存在连接。 (我可能不会默认启用它,但它是一个选项。)


    重新评论以下内容:

      

    我对你的代码有一个疑问:它确实会停止计时器,但我怎么知道它实际上已经破坏了TimerTest - 对象?计时器只是我告诉的一种方式。 AFAIK定时器本身没有对TimerTest - 对象的引用,那为什么该对象仍然存在?您可以通过修改Print()来打印“this.PanelID”`

    啊,但是计时器确实保留对TimerTest实例的引用:传递给计时器的函数是闭包。它引用了创建它的范围内的所有内容,包括引用self对象的TimerTest变量(当然,否则self.Print()将无效)。因此,只要该函数存在,它就会将TimerTest实例保留在内存中。

    那么是什么让这个功能在内存中保存?从计时器引用它。只要计时器处于活动状态,它就会引用该函数,并将该函数保存在内存中。在上面的示例中,clearInterval删除了对函数的引用,使其符合垃圾回收的条件;这意味着该函数不再保留它在内存中关闭的内容。如果函数是唯一具有对TimerTest实例的未完成引用的函数,则意味着TimerTest实例符合垃圾回收的条件。

    在我的代码中,没有任何内容将元素保留在内存中(可能不适用于您正在使用的定时器插件),但TimerTest实例保持活动状态只要浏览器的setInterval内容正在使用该函数。

    更多探索:


    现在你的小提琴并没有链接到那个(可能)狡猾的网站:

    我很惊讶地发现计时器插件似乎没有向元素添加任何内容以便于再次查找。无论如何,这对我们来说是微不足道的。

    更改Start以标记元素 - 这里我使用了一个类:

    TimerTest.prototype.Start = function()
    {
        var self = this;
    
        $("#" + this.PanelID)
            .addClass("hastimer")
            .everyTime("1s", "Test", function(){
                self.Print();
        });
    };
    

    (我也使用jQuery来查找元素,而不是document.getElementById。你想在函数表达式的末尾加上分号。)

    并更改Remove以停止该类的所有元素上的计时器:

    function Remove()
    {
        var main = $("#main");
        main.find(".hastimer").stopTime();
        main.html("I'm done!");
    }
    

    (在致电empty之前无需致电html; html致电empty。)

    Updated fiddle