javascript DOM 节点上的事件处理程序是否会随节点一起删除?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4337582/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-25 11:04:34  来源:igfitidea点击:

Do events handlers on a DOM node get deleted with the node?

javascriptdomjavascript-events

提问by Nathan Long

(Note: I'm using jQuery below, but the question is really a general Javascript one.)

(注意:我在下面使用 jQuery,但问题确实是一个通用的 Javascript 问题。)

Say I've got a div#formsectionwhose contents are repeatedly updated using AJAX, like this:

假设我有一个div#formsection使用 AJAX 重复更新其内容的文件,如下所示:

var formSection = $('div#formsection');
var newContents = $.get(/* URL for next section */);
formSection.html(newContents);

Whenever I update this div, I trigger a custom event, which binds event handlers to some of the newly-added elements, like this:

每当我更新这个 div 时,我都会触发一个自定义 event,它将事件处理程序绑定到一些新添加的元素,如下所示:

// When the first section of the form is loaded, this runs...
formSection.find('select#phonenumber').change(function(){/* stuff */});

...

// ... when the second section of the form is loaded, this runs...
formSection.find('input#foo').focus(function(){/* stuff */});

So: I'm binding event handlers to some DOM nodes, then later, deleting those DOM nodes and inserting new ones (html()does that) and binding event handlers to the new DOM nodes.

所以:我将事件处理程序绑定到一些 DOM 节点,然后删除这些 DOM 节点并插入新节点(html()这样做)并将事件处理程序绑定到新的 DOM 节点。

Are my event handlers deleted along with the DOM nodes they're bound to?In other words, as I load new sections, are lots of useless event handlers piling up in the browser memory, waiting for events on DOM nodes that no longer exist, or are they cleared out when their DOM nodes are deleted?

我的事件处理程序是否与它们绑定的 DOM 节点一起被删除?换句话说,当我加载新部分时,浏览器内存中是否堆积了大量无用的事件处理程序,等待不再存在的 DOM 节点上的事件,或者在删除它们的 DOM 节点时它们是否被清除?

Bonus question: how can test this myself?

额外问题:如何自己测试?

采纳答案by MooGoo

Event handler functions are subject to the same Garbage Collection that other variables are. That means they will be removed from memory when the interpreter determines that there is no possible means to obtain a reference to the function. Simply deleting a node however does not guarantee garbage collection. For instance, take this node and associated event handler

事件处理函数受制于与其他变量相同的垃圾收集。这意味着当解释器确定没有可能的方法获得对函数的引用时,它们将从内存中删除。然而,简单地删除一个节点并不能保证垃圾收集。例如,拿这个节点和关联的事件处理程序

var node = document.getElementById('test');
node.onclick = function() { alert('hai') };

Now lets remove the node from the DOM

现在让我们从 DOM 中删除节点

node.parentNode.removeChild(node);

So nodewill no longer be visible on your website, but it clearly still exists in memory, as does the event handler

Sonode将不再在您的网站上可见,但它显然仍然存在于内存中,事件处理程序也是如此

node.onclick(); //alerts hai

As long as the reference to nodeis still accessible somehow, it's associated properties (of which onclickis one) will remain intact.

只要引用node仍然可以以某种方式访问​​,它的关联属性(其中onclick之一)将保持不变。

Now let's try it without creating a dangling variable

现在让我们在不创建悬空变量的情况下尝试一下

document.getElementById('test').onclick = function() { alert('hai'); }

document.getElementById('test').parentNode.removeChild(document.getElementById('test'));

In this case, there seems to be no further way to access the DOM node #test, so when a garbage collection cycle is run, the onclickhandler should be removed from memory.

在这种情况下,似乎没有其他方法可以访问 DOM 节点 #test,因此当运行垃圾回收周期时,onclick应该从内存中删除处理程序。

But this is a very simple case. Javascript's use of closures can greatly complicate the determination of garbage collectability. Lets try binding a slightly more complex event handler function to onclick

但这是一个非常简单的案例。Javascript 对闭包的使用会使垃圾收集能力的确定变得非常复杂。让我们尝试绑定一个稍微复杂的事件处理函数到onclick

document.getElementById('test').onclick = function() {
  var i = 0;
  setInterval(function() {
    console.log(i++);
  }, 1000);

  this.parentNode.removeChild(this);
};

So when you click on #test, the element will instantly be removed, however one second later, and every second afterwards, you will see an incremented number printed to your console. The node is removed, and no further reference to it is possible, yet it seems parts of it remain. In this case the event handler function itself is likely not retained in memory but the scope it created is.

因此,当您单击 #test 时,该元素将立即被删除,但是一秒后,之后每一秒,您都会看到一个递增的数字打印到您的控制台。该节点被移除,无法进一步引用它,但它的一部分似乎仍然存在。在这种情况下,事件处理函数本身可能不会保留在内存中,而是它创建的范围。

So the answer I guess is; it depends. If there are dangling, accessible references to deleted DOM nodes, their associated event handlers will still reside in memory, along with the rest of their properties. Even if this is not the case, the scope created by the event handler functions might still be in use and in memory.

所以我猜的答案是;这取决于。如果存在对已删除 DOM 节点的悬空、可访问引用,则其关联的事件处理程序及其其余属性仍将驻留在内存中。即使情况并非如此,事件处理程序函数创建的作用域可能仍在使用中并在内存中。

In most cases (and happily ignoring IE6) it is best to just trust the Garbage Collector to do its job, Javascript is not C after all. However, in cases like the last example, it is important to write destructor functions of some sort to implicitly shut down functionality.

在大多数情况下(并且很高兴地忽略 IE6),最好只相信垃圾收集器来完成它的工作,Javascript 毕竟不是 C。然而,在像最后一个例子这样的情况下,编写某种析构函数来隐式关闭功能是很重要的。

回答by James Kovacs

jQuery goes to great lengths to avoid memory leaks when removing elements from the DOM. As long as you're using jQuery to delete DOM nodes, removal of event handlers and extra data should be handled by jQuery. I would highly recommend reading John Resig's Secrets of a JavaScript Ninjaas he goes into great detail on potential leaks in different browsers and how JavaScript libraries like jQuery get around these issues. If you're not using jQuery, you definitely have to worry about leaking memory through orphaned event handlers when deleting DOM nodes.

当从 DOM 中删除元素时,jQuery 竭尽全力避免内存泄漏。只要您使用 jQuery 删除 DOM 节点,事件处理程序和额外数据的删除就应该由 jQuery 处理。我强烈建议阅读 John Resig 的JavaScript Ninja秘密,因为他详细介绍了不同浏览器中的潜在泄漏以及 jQuery 等 JavaScript 库如何解决这些问题。如果您不使用 jQuery,则您肯定要担心在删除 DOM 节点时会通过孤立事件处理程序泄漏内存。

回答by Nathan Long

Not necessarily

不必要

The documentation on jQuery's empty()methodboth answers my question and gives me a solution to my problem. It says:

jQueryempty()方法文档既回答了我的问题,又为我提供了解决问题的方法。它说:

To avoid memory leaks, jQuery removes other constructs such as data and event handlers from the child elements before removing the elements themselves.

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

So: 1) if we didn't do this explicitly, we'd get memory leaks, and 2) by using empty(), I can avoid this.

所以:1)如果我们没有明确地这样做,我们会得到内存泄漏,2)通过使用empty(),我可以避免这种情况。

Therefore, I should do this:

因此,我应该这样做:

formSection.empty();
formSection.html(newContents);

It's still not clear to me whether .html()would take care of this by itself, but one extra line to be sure doesn't bother me.

我仍然不清楚是否.html()会自己解决这个问题,但可以肯定的是,额外的一行不会打扰我。

回答by Martin Algesten

You may need to remove those event handlers.

您可能需要删除这些事件处理程序。

Javascript memory leaks after unloading a web page

卸载网页后Javascript内存泄漏

In our code, which is not based on jQuery, but some prototype deviant, we have initializers and destructors in our classes. We found it's absolutely essential to remove event handlers from DOM objects when we destroy not only our application but also individual widgets during runtime.

在我们的代码中,它不是基于 jQuery,而是一些原型异常,我们的类中有初始化器和析构器。我们发现当我们在运行时不仅销毁我们的应用程序而且销毁单个小部件时,从 DOM 对象中删除事件处理程序是绝对必要的。

Otherwise we end up with memory leaks in IE.

否则我们最终会在 IE 中出现内存泄漏。

It's surprisingly easy to get memory leaks in IE - even when we unload the page, we must be sure the application "shuts down" cleanly, tidying away everything - or the IE process will grow over time.

在 IE 中发生内存泄漏非常容易——即使我们卸载页面,我们也必须确保应用程序干净利落地“关闭”,清理所有内容——否则 IE 进程会随着时间的推移而增长。

Edit:To do this properly we have an event observer on windowfor the unloadevent. When that event comes, our chain of destructors is called to properly clean up every object.

编辑:为了正确地做到这一点,我们window为该unload事件设置了一个事件观察者。当该事件发生时,我们的析构函数链被调用以正确清理每个对象。

And some sample code:

和一些示例代码:

/**
 * @constructs
 */
initialize: function () {
    // call superclass
    MyCompany.Control.prototype.initialize.apply(this, arguments);

    this.register(MyCompany.Events.ID_CHANGED, this.onIdChanged);
    this.register(MyCompany.Events.FLASHMAPSV_UPDATE, this.onFlashmapSvUpdate);
},

destroy: function () {

    if (this.overMap) {
        this.overMap.destroy();
    }

    this.unregister(MyCompany.Events.ID_CHANGED, this.onIdChanged);
    this.unregister(MyCompany.Events.FLASHMAPSV_UPDATE, this.onFlashmapSvUpdate);

    // call superclass
    MyCompany.Control.prototype.destroy.apply(this, arguments);
},

回答by user1736525

I wanted to know myself so after a little test, I think the answer is yes.

我想了解自己,所以经过一些测试,我认为答案是肯定的。

removeEvent is called when you .remove() something from the DOM.

当您从 DOM 中执行 .remove() 某些内容时,会调用 removeEvent。

If you want see it yourself you can try this and follow the code by setting a breakpoint. (I was using jquery 1.8.1)

如果你想自己看,你可以试试这个,并通过设置断点来遵循代码。(我使用的是 jquery 1.8.1)

Add a new div first:
$('body').append('<div id="test"></div>')

Check $.cacheto make sure there is no events attached to it. (it should be the last object)

Attach a click event to it:
$('#test').on('click',function(e) {console.log("clicked")});

Test it and see a new object in $.cache:
$('#test').click()

Remove it and you can see the object in $.cacheis gone as well:
$('#test').remove()

首先添加一个新的 div:
$('body').append('<div id="test"></div>')

检查$.cache以确保没有事件附加到它。(它应该是最后一个对象)

给它附加一个点击事件:
$('#test').on('click',function(e) {console.log("clicked")});

测试它并看到一个新对象$.cache
$('#test').click()

删除它,你可以看到对象$.cache也消失了:
$('#test').remove()