【问题标题】:addEventListener memory leak due to frames由于帧导致的 addEventListener 内存泄漏
【发布时间】:2012-12-03 05:20:25
【问题描述】:

我有一个 GreaseMonkey 脚本,该脚本在一个使用框架作为其界面的组成部分的网站上运行。该脚本像筛子一样泄漏内存,我相信这是由于我在其中一个帧中使用了 addEventListener 造成的。很简单,我附加了各种事件侦听器,然后重新加载框架并附加事件侦听器,然后重新加载框架,当您与该框架或其他框架中的各种元素交互时,围绕数百甚至可能数千次迭代。到最后,Firefox 的内存从约 300M 增加到了 2G(或在到达之前崩溃)。

我在某处读到,重新加载整个页面将允许 FireFox 的垃圾收集例程启动并从孤立的事件处理程序中恢复所有内存,并且当我在脚本运行一段时间后按 F5 时确定,大约 10秒内存回落到300M。不幸的是,这破坏了网站中的另一个框架(一个非常受欢迎的聊天窗口),所以虽然它似乎证实了我对 addEventListener 的怀疑,但这并不是一个真正的解决方案。

我还有什么办法可以在不强制刷新整页的情况下正确释放内存吗?

(目前使用的是 GM 1.5 和 FF 17,但从 GM 0.8/FF 4 左右开始就存在问题。)

【问题讨论】:

  • 而不是从框架内添加事件侦听器(每次重新加载框架时),只需在框架外的代码中添加一次侦听器。在框架内定义与您要处理的每个事件对应的函数,并让每个事件处理程序(框架外)调用框架内的相应函数。
  • @Lee,您愿意勾勒出该代码的外观吗?另外,我将如何处理偶尔来来去去的元素?从本质上讲,该窗格是一个状态窗格,当您的状态发生变化时,会出现新元素,然后我会将一些事件附加到这些元素上。 (也有永久元素,我非常期待尝试你的方法。:-))
  • Lee 的方法如果是跨域就行不通。 ...尚不清楚addEventListener 是一个/问题,但如果您对jQuery 持开放态度,则可能有一个jQuery 解决方法。如果您在禁用 Greasemonkey 脚本的情况下使用该网站至少 30 分钟会发生什么?
  • @Brock,所有活动都在一个域内。我绝对对 jQuery 持开放态度。当我在没有启用 GM 的情况下使用该站点时,根本没有泄漏。
  • 另外,我完全愿意接受您可以推荐的任何其他故障排除技术,以缩小问题的根源。 :-)

标签: javascript memory greasemonkey addeventlistener


【解决方案1】:

没有看到您的完整脚本或Short, Self Contained, Compilable Example,我们无法确定发生了什么。可能addEventListener 不是问题。

以下是一些更好的代码策略,内存泄漏更少:

  1. 内联/匿名函数通常是罪魁祸首,尤其是事件处理程序。

    差/泄漏:

    elem.onclick = function () {/*do something*/};
    elem.addEventListener ("click", function() {/*do something*/}, false);
    $("elem").click ( function () {/*do something*/} );
    

    不会泄漏,也更容易维护:

    elem.onclick = clickHandler;
    elem.addEventListener ("click", clickHandler, false);
    $("elem").click (clickHandler);
    
    function clickHandler (evt) {
        /*do something*/
    }
    

    请注意,对于用户脚本,您应该avoid onclick, etc. anyway.

  2. 同样不要在 HTML 属性上使用 JS。 EG不要使用<span onclick="callSomeFunction()">等。

  3. 将在 iframe 中运行的代码最小化为您明确需要的代码。

    1. 使用 @include@exclude@match 指令尽可能多地阻止不需要的 iframe。
    2. Wrap all code that doesn't need to run in iframes in a block 像这样:

      if (window.top === window.self) {
        // Not in a frame
      }
      
  4. 不要使用innerHTML

  5. 对于很多元素,或者与 AJAX 一起来来去去的元素,不要使用 addEventListener() 或 jQuery 的 .bind().click() 等。
    这会跨数千个节点复制侦听器。

    使用jQuery's .on()。这样,监听器只连接一次,并通过冒泡适当地触发。 (请注意,在一些罕见的情况下,.on() 可能会被页面的 javascript 阻止。)

    在你的情况下,你可能想要这样的东西:

    $(document).on ("click", "YOUR ELEM SELECTOR", clickHandler);
    
    function clickHandler (evt) {
        /*do something*/
    }
    
  6. 为避免意外的循环引用或孤立项,请使用 jQuery 添加或删除元素,而不是直接使用 DOM 方法,如 createElement()appendChild() 等。
    jQuery 被设计/测试以最小化此类事情。

  7. 谨防过度使用GM_setValue()。它很容易使用大量全局资源或导致脚本实例崩溃。

    1. 对于同域值,请使用localStorage
    2. 不要使用GM_setValue() 来存储字符串以外的任何内容。对于其他任何事情,请使用诸如GM_SuperValue 之类的序列化程序。即使是看起来很无辜的整数也可能导致默认的GM_setValue() 崩溃。
    3. 与其存储大量小变量,不如将它们包装在一个对象中并使用其中一个序列化程序存储 那个


  8. 始终检查返回值并假设可能缺少元素:
    这是(唉,很典型):

    $("selector").text($("selector").text().match(/foo=([bar]+)/)[1]);
    

    更好:

    var salesItemDiv    = $("selector");
    var fooMatch        = salesItemDiv.text ().match (/\bfoo\s*=\s*([bar]+)\b/i);
    if (fooMatch  &&  fooMatch.length > 1) {
        salesItemDiv.text ( fooMatch[1] );
    }
    

    可能后跟:

    salesItemDiv = fooMatch = null;
    

    见下文。

  9. 注意递归/内联setTimeout() 调用。使用setInterval() 进行重复计时。就像事件处理程序一样,不要使用内联/匿名函数。

  10. 通过JSLint 运行您的代码。

  11. 避免使用eval()auto/hidden eval() invocations

  12. 完成后将变量设置为nullSee this, for example.

  13. 参考:"Do you know what may cause memory leaks in JavaScript?"

  14. Additional reading on JS memory leaks

  15. Mozilla Performance: Leak Tools

【讨论】:

  • 牛逼!我无法想象我的脚本在只遵循了 1/4 的建议后不会有显着改善。 :-) (我当然知道它存在您列出的一些问题。)如果我仍然有问题(不幸的是,它是一个庞然大物)我将整理您要求的 SSCCE,但我将把它作为一个单独的问题。
猜你喜欢
  • 1970-01-01
  • 2016-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-10
  • 1970-01-01
  • 2011-03-03
  • 2013-07-23
相关资源
最近更新 更多