如何在不造成内存泄漏的情况下删除DOM元素?

51

我的JavaScript代码创建了一个LI元素列表。当我更新列表时,内存用量会增加并且从未下降。我在sIEve中进行了测试,并显示浏览器保留了所有应该通过$.remove()$.empty jQuery命令删除的元素。

如何才能在不造成内存泄漏的情况下删除DOM节点?

请参阅我的其他问题以查看具体代码。

5个回答

46

DOM会保留所有DOM节点,即使它们已经从DOM树中被移除,唯一的删除这些节点的方法就是进行页面刷新(如果将列表放入iframe中,则刷新不会那么明显)。

否则,你可以等待问题变得严重到浏览器的垃圾回收程序被迫采取行动(这里需要数百兆字节的未使用节点)。

最佳实践是重用节点。

编辑:尝试这个:

var garbageBin;
window.onload = function ()
    {
    if (typeof(garbageBin) === 'undefined')
        {
        //Here we are creating a 'garbage bin' object to temporarily 
        //store elements that are to be discarded
        garbageBin = document.createElement('div');
        garbageBin.style.display = 'none'; //Make sure it is not displayed
        document.body.appendChild(garbageBin);
        }
    function discardElement(element)
        {
        //The way this works is due to the phenomenon whereby child nodes
        //of an object with it's innerHTML emptied are removed from memory

        //Move the element to the garbage bin element
        garbageBin.appendChild(element);
        //Empty the garbage bin
        garbageBin.innerHTML = "";
        }
    }

要在您的上下文中使用它,您需要按照以下方式进行:

discardElement(this);

9
我不知道“DOM保留所有DOM节点,即使它们已从DOM树本身中删除”。你的意思是$.remove()函数什么也没做吗? - podeig
2
在sIEve测试后,我可以说它有助于删除垃圾节点。但是当我在IE8和FF中进行测试时,内存使用量仍然增加。Andrew,appendChild是做什么的?将节点移动到垃圾div中吗?在discadElement()之前应该运行$.unbind()吗? - podeig
2
垃圾箱需要添加到文档正文中吗? - Guillermo Moscoso
11
这个回答很令人困惑且含义模糊。它说页面刷新是“唯一的方法”来移除节点,但同时又暗示浏览器的垃圾回收将释放这些节点的内存。如果这是真的,那么刷新不是“唯一”的方法。此外,从开发者的角度来看,应该没有区别可以被垃圾回收的对象和已经被垃圾回收的对象之间,这正是自动内存管理的全部意义——当GC运行时,应该对用户不可见。 - Max Wallace
2
“DOM 保留所有 DOM 节点” 是指实现 DOM 规范的浏览器会在 DOM 树之外存储对这些节点的引用,还是仅仅意味着这些节点会一直停留在内存中,直到下一次垃圾回收运行?答案暗示了后者,但如果这些节点能够被垃圾回收,那么它们就没有以任何方式被“保留”。 - Max Wallace
显示剩余2条评论

17

这更像是一条提示而非实际答案,但它也相当有趣。

来自W3C DOM核心规范(http://www.w3.org/TR/DOM-Level-2-Core/core.html):

  

核心DOM API旨在与各种语言兼容,包括普通用户脚本语言和大多由专业程序员使用的更具挑战性的语言。因此,DOM API需要在各种内存管理哲学之间运行,从根本上不公开内存管理的语言绑定,通过(特别是Java)提供显式构造函数但提供自动垃圾收集机制以自动回收未使用的内存,到那些(尤其是C / C ++)通常要求程序员明确分配对象内存,追踪其使用情况并显式释放以供重用。为了确保这些平台上的一致API,DOM根本不涉及内存管理问题,而是将这些留给实现。 DOM API定义的任何显式语言绑定(用于ECMAScript和Java)都不需要任何内存管理方法,但其他语言(尤其是C或C ++)的DOM绑定可能需要此类支持。这些扩展将由适应DOM API到特定语言的人负责,而不是DOM工作组。

换句话说:内存管理由不同语言中DOM规范的实现来处理。要找出从javascript中删除DOM对象的任何方法(而不是hack),您必须查阅DOM实现的文档。(但是,MDC网站上关于该主题的信息非常少。)


关于jQuery#removejQuery#empty的说明:据我所知,这两种方法都没有除了从DOM node 中删除 Object 或从文档中删除DOM node 之外的其他操作。它们只是移除。当然,这并不意味着这些对象没有分配内存(即使它们不在 document 中)。

编辑:上面的内容多余了,因为显然jQuery不能奇迹般地解决并绕过所用浏览器的DOM实现。


2
从jQuery .remove() API中翻译:"...除了元素本身之外,还会删除与这些元素相关联的所有绑定事件和jQuery数据。如果要删除元素而不删除数据和事件,请改用.detach()。" - vsync
你说过:“据我所知,这两种方法除了从DOM节点中移除对象或从文档中移除DOM节点之外,似乎并没有做其他的事情。” 所以我引用了API中的内容来证明它确实做了更多的事情。它还会移除所有绑定的事件。 - vsync
1
上下文是内存分配。但是,好吧,在那里我划掉了关于 jQuery 的段落。 - FK82

9

我甚至针对每个元素执行$(this).unbind().html("").remove(); - podeig
2
我以为当你使用element.remove()时,jQuery会自动删除事件处理程序,不是吗? - jayarjo
1
谢谢@skilldrick!10年后,你的建议让我走上了正确的道路(我正在使用原生JS;没有涉及jQuery):-D - Fred

2
下面的代码在我的IE7和其他浏览器上没有泄漏:
<html>
<head></head>
<body>
    <a href="javascript:" onclick="addRemove(this)">add</a>
    <ul></ul>
    <script>
        function addRemove(a) {
            var ul = document.getElementsByTagName('UL')[0],
                li, i = 20000;
            if (a.innerHTML === 'add') {
                while (i--) {
                    li = document.createElement('LI');
                    ul.appendChild(li);
                    li.innerHTML = i;
                    li.onclick = function() {
                        alert(this.innerHTML);
                    };
                }
                a.innerHTML = 'remove';
            } else {
                while (ul.firstChild) {
                    ul.removeChild(ul.firstChild);
                }
                a.innerHTML = 'add';
            }
        }
    </script>
    </body>
</html>

也许你可以尝试与你的代码进行比较,我知道当你先将节点插入DOM中,然后再对其进行操作时(例如:附加事件或填充其 innerHTML 属性),IE泄漏要少得多。


0

两个链接都失效了,我会给你点踩。 - Bobby Jack
我已经修复了Crockford的链接,但是我不知道另一个是什么。 - Bobby Jack
固定闭包链接,使用Web存档 - Ankur Parihar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接