JavaScript中添加/移除监听器(垃圾收集器)

7
我有一个关于添加/移除DOM对象监听器的问题。 我想问一下,当从页面中删除元素时,垃圾回收器能否收集内存。
例如:一个带有几个子项(
  • )的
      标签。
      var ul = document.getElementById('someParent');
      var children = ul.children;
      var someFunction = function() {};
      
      for(var i = 0; i < children.length; i++) {
        children[i].addEventListener('click', someFunction);
      }
      
      
      // This is where I am not sure, would the garbage collector be able to collect
      // the memory allocated on adding listener on children,
      // if I remove the ul tag?
      ul.remove();
      

  • 你的循环不会正常工作。children.length 不是一个好的条件。 - Luka
    只是一个笔误,谢谢 :) - olanchuy
    3个回答

    4

    ul.remove(); 会从DOM中移除 ul 元素及其所有子元素。但是只要您在变量 children, someFunctionul 中具有对这些监听器、li 元素和 ul 元素的引用,事件监听器的内存就不会被释放。

    如果您希望让垃圾回收器清理所有这些内容,可以执行以下操作:

    ul.remove();
    children = null;
    someFunction = null;
    ul = null;
    

    这样,变量children就不会持有对这些元素的引用了。如果您的代码中没有任何其他变量持有对这些元素的引用,垃圾回收器将会清理它们。 对于someFunction也是同样的情况。请注意,ul元素持有其所有子元素和监听器的所有引用,因此还需要清除ul


    我明白了,这很有道理。如果它是一个匿名函数,那就没问题了,对吧? - olanchuy
    是的,对于匿名函数来说这样做没问题。只要确保children也被清理即可。 - Luka
    我明白了,之前我的脑海中有一个疑问,现在已经解决了。谢谢你的帮助。 :) - olanchuy
    “Element.children”是一个实时列表,但是我不认为它会在元素从DOM中移除后继续包含对这些元素的引用。虽然我找不到任何相关的参考资料,但我的理解可能是错误的。 - lonesomeday
    @lonesomeday,我假设你的示例中Elementulul.children确实是一个动态列表。但这意味着,如果您从DOM中删除ul元素的某些子项,它将在该列表中反映出来。但是如果您删除DOM中的ul(就像在示例中),那么该列表将保持不变。 - Luka

    2
    完全不用担心,您可以在其他地方访问ul变量。下面的示例说明了这一点。
    var ul = document.getElementById('someParent');
    ul.remove();
    console.log(ul); // ul and all li tags
    document.body.appendChild(ul); // ul appears again
    

    这个例子并不是一般情况。一般情况下,您想在事件中(如“按钮单击”)访问DOM引用。只要事件没有解除绑定,该引用将始终可用。例如:

    var ul = document.getElementById('someParent');
    var myButton = document.getElementById('myButton');
    myButton.addEventListener('click', function () {
        ul.innerHTML += '<li>Some text</li>'
    });
    ul.remove();
    myButton.click(); // no exception when execting button click event
    

    为了避免JS内存泄漏:

    1. 确保没有DOM引用,例如使用jQuery。
    2. 如果在应用程序中使用DOM引用,请在不使用时将其设置为null或undefined。

    因此,您可以稍微修改您的事件,如下所示。

    myButton.addEventListener('click', function () {
        // check ul belongs to visible DOM
        if (!document.body.contains(ul)) {
            ul = null;
            return;
        }
        ul.innerHTML += '<li>Some text</li>'
    });
    

    1

    如果有人能为我确认这一点,我会很感激。

    据我的理解,是的,它将被垃圾回收。根据MDN内存管理所述:

    垃圾回收算法依赖于引用的概念。在内存管理的上下文中,如果一个对象可以访问另一个对象(无论是隐式还是显式),则称前者引用后者。例如,JavaScript对象具有对其原型(隐式引用)和其属性值(显式引用)的引用。

    基本上,如果DOM找不到对该元素的引用,它将在不久的将来进行垃圾回收。查看DOM标准规范中.remove()如何工作的说明,它将运行一系列步骤,删除所有对该元素的引用并设置父级和兄弟节点的新索引。

    由于没有对该元素的引用,它将被垃圾回收。在您的示例中,您仅向创建的特定
      元素的子元素添加了eventListeners。当您删除一个元素时,还会删除其所有子元素的引用(我认为这在上面链接中的步骤中更清晰,其中他们实际上将父索引设置为指向自身)。
      编辑:@contrabit是正确的,我没有看到您将children存储在变量中。只要有对它们的引用,它们就不会被垃圾回收。

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