如果一个DOM元素被删除,它的监听器是否也从内存中删除?

如果一个DOM元素被删除,它的监听器是否也从内存中删除?

现代浏览器

普通的JavaScript

如果被移除的DOM元素是无引用的(没有引用指向它),那么 – 元素本身被垃圾回收器拾取,以及任何与其关联的事件处理程序/侦听器。

var a = document.createElement('div'); var b = document.createElement('p'); // Add event listeners to b etc... a.appendChild(b); a.removeChild(b); b = null; // A reference to 'b' no longer exists // Therefore the element and any event listeners attached to it are removed. 

然而; 如果仍有指向所述元素的引用,则将元素及其事件侦听器保留在内存中。

 var a = document.createElement('div'); var b = document.createElement('p'); // Add event listeners to b etc... a.appendChild(b); a.removeChild(b); // A reference to 'b' still exists // Therefore the element and any associated event listeners are still retained. 

jQuery的

假设jQuery中的相关方法(如remove() )能够以完全相同的方式运行(考虑remove()是使用removeChild()编写的,这将是公平的。

但是, 这是不正确的 。 jQuery库实际上有一个名为cleanData()的内部方法(这个方法没有logging,理论上可以在任何时候改变cleanData() (这个方法看起来像这样 ),在清除时自动清理所有与元素相关的数据/事件从DOM(这是通过remove()empty()html("")等)。


较旧的浏览器

旧版本的浏览器 – 尤其是IE的老版本 – 已知有内存泄漏问题,因为事件监听器保持引用它们所附的元素的引用。

如果您想深入了解用于修复传统IE版本内存泄漏的原因,模式和解决scheme,我强烈build议您阅读此MSDN文章“了解并解决Internet Explorer泄漏模式”。

还有一些与此相关的文章:

  • JScript内存泄漏
  • 内存泄漏在IE8中
  • JavaScript内存泄漏

在这种情况下手动移除听众可能是一个很好的习惯(只有当内存对于你的应用程序至关重要,而你实际上是以这种浏览器为目标的话)。

关于jQuery:

.remove()方法从DOM中取出元素。 当你想删除元素本身,以及它里面的一切时,使用.remove()。 除了元素本身之外,所有与这些元素相关联的绑定事件和jQuery数据都将被删除。 要删除元素而不删除数据和事件,请改用.detach()。

参考: http : //api.jquery.com/remove/

jQuery v1.8.2 .remove()源代码:

 remove: function( selector, keepData ) { var elem, i = 0; for ( ; (elem = this[i]) != null; i++ ) { if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { if ( !keepData && elem.nodeType === 1 ) { jQuery.cleanData( elem.getElementsByTagName("*") ); jQuery.cleanData( [ elem ] ); } if ( elem.parentNode ) { elem.parentNode.removeChild( elem ); } } } return this; } 

显然jQuery使用node.removeChild()

根据这个: https : //developer.mozilla.org/en-US/docs/DOM/Node.removeChild ,

The removed child node still exists in memory, but is no longer part of the DOM. You may reuse the removed node later in your code, via the oldChild object reference.

即事件侦听器可能会被删除,但node仍然存在于内存中。

在这里输入图像说明

请注意,由于Chrome DevTools中的错误,我必须按“强制GC”图标两次。 我以前的gif是因为这个误导。

泄漏memory.html:

 <script> function run() { function clicked(e) { console.log(e); } for (var i = 0; i < 1000; i++) { var span = document.createElement('span'); span.addEventListener('click', clicked, false); document.body.appendChild(span); } setTimeout(function() { var spans = document.querySelectorAll('span'); for (var i = 0; i < spans.length; i++) { var span = spans[i]; span.parentNode.removeChild(span); } }, 100); } </script> <button onclick="run()">Run (memory leak)</button> 

无记忆leak.html

 <script> function run() { function clicked(e) { console.log(e); } for (var i = 0; i < 1000; i++) { var span = document.createElement('span'); span.addEventListener('click', clicked, false); document.body.appendChild(span); } setTimeout(function() { var spans = document.querySelectorAll('span'); for (var i = 0; i < spans.length; i++) { var span = spans[i]; span.removeEventListener('click', clicked, false); span.remove(); } }, 100); } </script> <button onclick="run()">Run (no memory leak)</button> 

不要犹豫,看看堆在事件处理程序中看到内存泄漏,通过闭包保持对元素的引用,并且元素保持对事件处理程序的引用。

垃圾收集器不喜欢循环引用。

通常的内存泄漏情况:承认一个对象有一个元素的引用。 该元素具有处理程序的引用。 处理程序有一个引用的对象。 该对象引用了很多其他对象。 这个对象是你认为通过从集合中引用它而抛弃的集合的一部分。 =>整个对象,它所涉及的所有对象都将保留在内存中,直到页面退出。 =>你必须考虑一个完整的对象类的杀戮方法,或者相信一个mvc框架。

此外,不要犹豫,使用Chrome开发工具的Retaining树部分。

只是扩展其他答案…

删除元素后,委派的事件处理程序将不会被删除。

 $('body').on('click', '#someEl', function (event){ console.log(event); }); $('#someEL').remove(); // removing the element from DOM 

现在检查:

 $._data(document.body, 'events'); 

关于jQuery ,下面的常见方法也将删除其他的结构,如数据和事件处理程序:

去掉()

除了元素本身之外,所有与这些元素相关联的绑定事件和jQuery数据都将被删除。

空()

为了避免内存泄漏,jQuery在删除元素本身之前从子元素中删除其他构造,如数据和事件处理程序。

HTML()

另外,在用新内容replace那些元素之前,jQuery从子元素中删除其他构造,例如数据和事件处理器。

是的,垃圾收集器也会将它们删除。 尽pipe如此,传统浏览器并不总是如此。