【问题标题】:Do events handlers on a DOM node get deleted with the node?DOM 节点上的事件处理程序是否会随节点一起删除?
【发布时间】:2011-05-19 06:21:16
【问题描述】:

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

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

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

每当我更新这个 div 时,我都会trigger a custom 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 */});

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

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

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

【问题讨论】:

  • 我们能否将其改写为“Do event handlers on a DOM node...”?我更愿意将 event 视为对 event handler 的实际调用——我什至可以将 event object 称为事件,但肯定不是事件处理程序。
  • @Martin Algesten - 好点,这不仅仅是偏好 - 我的措辞不正确,而你的措辞是正确的。 :) 会更新。
  • 我对这个问题的答案非常感兴趣,但作为一个切线,避免这个问题的一种方法是利用事件冒泡。不是监听特定 DOM 节点上的事件,而是监听父节点(保持不变),然后识别事件的原始来源。这样,您就不必不断地将事件处理程序添加到新的 DOM 节点。 jQuery.live 在这方面非常有用。
  • 哇!现在我可以回答了:)
  • 视情况而定。如果您有一个包含 1000 行的表,则在表本身上注册一个单击处理程序要简单得多,然后读取 event.target 属性以确定单击了哪一行。这不是为创建的每个新行分配处理函数。这当然可以被抽象掉(就像 jQuery 的 live 函数一样),但是无论如何增加的复杂性仍然存在。

标签: javascript dom javascript-events


【解决方案1】:

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

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

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

node.parentNode.removeChild(node);

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

node.onclick(); //alerts hai

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

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

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

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

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

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

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

  this.parentNode.removeChild(this);
};

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

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

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      Javascript memory leaks after unloading a web page

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

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

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

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

      还有一些示例代码:

      /**
       * @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);
      },
      

      【讨论】:

      • 有趣的是,即使在卸载后您仍然需要清理。不知道。
      • 您能否提供一些示例代码来说明如何删除事件?
      • 似乎 jQuery 可以帮助 jQuery.unbind() 解除绑定 everything .bind()jQuery.die() 对于任何东西 .live()
      • 但是你怎么办,因为你不使用 jQuery? :)
      • 为什么感觉像是在编写 C 语言却忘了处理 malloc
      【解决方案4】:

      不一定

      documentation on jQuery's empty() method 既回答了我的问题,也为我的问题提供了解决方案。它说:

      为避免内存泄漏,jQuery 移除 其他结构,如数据和 来自子元素的事件处理程序 在移除元素之前 自己。

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

      因此,我应该这样做:

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

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

      【讨论】:

      • 我认为将 jQuery 与此讨论区分开来很重要。 jQuery 有自己的存储事件的方法,独立于任何内置的浏览器机制。重要的是,它将与节点关联的事件和其他数据存储在不直接链接到节点的单独映射中。所以这有点像说当你delete a 你也应该delete variable_associated_with_a
      【解决方案5】:

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

      当你 .remove() 从 DOM 中删除某些东西时会调用removeEvent。

      如果您想自己查看,可以尝试此操作并通过设置断点来遵循代码。 (我使用的是 jquery 1.8.1)

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

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

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

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

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

      【讨论】:

        猜你喜欢
        • 2011-03-02
        • 1970-01-01
        • 2012-01-22
        • 1970-01-01
        • 2014-05-12
        • 2021-05-30
        • 1970-01-01
        • 2017-08-06
        • 2014-05-18
        相关资源
        最近更新 更多