如何在没有内存泄漏的情况下删除 DOM 元素?

Posted

技术标签:

【中文标题】如何在没有内存泄漏的情况下删除 DOM 元素?【英文标题】:How to remove DOM elements without memory leaks? 【发布时间】:2011-04-16 15:53:44 【问题描述】:

我的 JavaSript 代码构建了一个 LI 元素列表。当我更新列表时,内存使用量会增长并且永远不会下降。我在 sIEve 中进行了测试,它显示浏览器保留了所有应该被 $.remove()$.empty jQuery 命令删除的元素。

我应该怎么做才能删除没有内存泄漏的 DOM 节点?

具体代码见my other question。

【问题讨论】:

【参考方案1】:

如果您必须“后修复”泄漏,并且必须在不重写所有代码以考虑闭包、循环引用等的情况下这样做,请在删除之前使用 Douglas Crockfords Purge-method:

https://crockford.com/javascript/memory/leak.html

或者使用这个闭包修复解决方法:

Leak Free Javascript Closures

【讨论】:

两个链接都坏了;否决 我已经修复了 Crockford 链接,但我不知道另一个是什么 固定关闭链接,使用网络存档【参考方案2】:

这更像是一个仅供参考而不是实际答案,但它也很有趣。

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

Core DOM API 旨在与多种语言兼容,包括普通用户脚本语言和主要由专业程序员使用的更具挑战性的语言。因此,DOM API 需要跨越各种内存管理理念进行操作,从根本不向用户公开内存管理的语言绑定,到提供显式构造函数但提供自动垃圾收集机制的那些(尤其是 Java)回收未使用的内存,对于那些通常需要程序员显式分配对象内存、跟踪它的使用位置并显式释放它以供重用的内存(尤其是 C/C++)。为了确保跨这些平台的 API 一致,DOM 根本不解决内存管理问题,而是将这些留给实现。 DOM API(用于 ECMAScript 和 Java)定义的显式语言绑定都不需要任何内存管理方法,但其他语言(尤其是 C 或 C++)的 DOM 绑定可能需要这种支持。这些扩展将是那些将 DOM API 适应特定语言的人的责任,而不是 DOM 工作组的责任。

换句话说:内存管理留给各种语言的 DOM 规范的实现。您必须查看 javascript 中 DOM 实现的文档,以找出从内存中删除 DOM 对象的任何方法,这不是 hack。 (然而,MDC 网站上关于该主题的信息非常少。)


作为 jQuery#removejQuery#empty 的注释:据我所知,除了从 DOM nodes 中删除 Objects 或从document。他们只删除 当然并不意味着没有为这些对象分配内存(即使它们不再在document 中)。

编辑:上述段落是多余的,因为显然 jQuery 无法创造奇迹,也无法绕过所用浏览器的 DOM 实现。

【讨论】:

来自 jQuery .remove() API:“...除了元素本身,所有绑定的事件和与元素关联的 jQuery 数据都将被删除。要删除元素而不删除数据和事件,使用 .detach() 代替。” 您说“据我所知,除了从 DOM 节点中删除对象或从文档中删除 DOM 节点之外,这些方法都没有做任何事情”..所以我从 API 中引用它做的更多比起那个来说。它还会删除所有绑定事件。 上下文是内存分配。但是哦,好吧,我在jQuery 上删除了这段话。【参考方案3】:

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);

【讨论】:

我不知道“DOM 保留所有 DOM 节点,即使它们已从 DOM 树本身中删除”你的意思是 $.remove() 函数没有做任何事情吗? 在 sIEve 中测试后,我可以说它有助于删除垃圾节点。但是当我在 IE8 和 FF 中测试时,内存使用量会增加。安德鲁, appendChild 做了什么?将节点移动到垃圾 div?我应该在 discadElement() 之前运行 $.unbind() 吗? 文档正文需要添加垃圾箱吗? 这个答案令人困惑和模棱两可。它说页面刷新是删除节点的“唯一方法”,但这也意味着浏览器的垃圾收集将释放这些节点的内存。如果为真,那么刷新不是“唯一”的方式。此外,从开发人员的角度来看,可以被垃圾回收的对象和已经被垃圾回收的对象之间应该没有区别——这就是自动内存管理的全部意义——当 GC 运行时应该对用户。 “DOM 保留所有 DOM 节点”是否意味着实现 DOM 规范的浏览器实际上会将对这些节点的引用存储在 DOM 树之外,或者只是这些节点将在内存中徘徊直到下一次垃圾收集器运行?答案暗示后者,但是如果这些节点能够被垃圾收集,那么它们就不会在任何意义上“保留”。【参考方案4】:

以下代码在我的 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 执行任何操作之前先将节点插入 DOM 时,IE 的泄漏要少得多,例如:将事件附加到它或填充其 innerHTML 属性。

【讨论】:

【参考方案5】:

您是否删除了任何事件侦听器?那个can cause memory leaks。

【讨论】:

我什至对每个元素都这样做 $(this).unbind().html("").remove(); 我认为 jQuery 会自动删除事件处理程序,当您执行 element.remove() 时,不是吗? 谢谢@skilldrick! 10 年后,你的建议让我走上了正确的道路(我正在做 vanilla JS;不涉及 jQuery):-D

以上是关于如何在没有内存泄漏的情况下删除 DOM 元素?的主要内容,如果未能解决你的问题,请参考以下文章

内存泄漏

在没有虚拟析构函数的情况下删除基类指针会导致内存泄漏吗?

如何在没有内存泄漏的情况下在 ARC 中的 UIView 之间切换?

如何在没有内存泄漏的情况下使用 +[NSException raise:format:arguments:]?

Node.js 刮板中的内存泄漏

ThreadLocal和内存泄漏