由于帧而导致的addEventListener内存泄漏

时间:2012-12-03 05:20:25

标签: javascript memory greasemonkey addeventlistener

我有一个GreaseMonkey脚本,适用于使用框架作为其界面不可分割的一部分的网站。这个脚本像筛子一样泄漏内存,我相信这是因为我在其中一个框架中使用了addEventListener。很简单,我附加了各种事件监听器,然后框架重新加载并附加事件监听器,然后当您与此框架中的各种元素或其他元素交互时,框架会重新加载数周或数千次迭代。到最后,Firefox已经从大约300万内存增加到2G(或者在它到达之前崩溃)。

我在某处读到,执行整页重新加载将允许FireFox的垃圾收集例程启动并从孤立的事件处理程序中恢复所有内存,当我在脚本运行一段时间后,在大约10秒内点击F5时就足够了秒内存减少到300M。不幸的是,这打破了网站中的一个不同的框架(一个非常流行的聊天窗口),所以虽然它似乎证实了我怀疑addEventListener应该受到责备,但它并不是一个真正的选择作为解决方案。

在不强制整页刷新的情况下,我还能做些什么来正确释放内存吗?

(目前使用的是GM 1.5和FF 17,但问题是自GM 0.8 / FF 4以后就存在这个问题。)

1 个答案:

答案 0 :(得分:9)

如果没有看到完整的脚本或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添加或删除元素,而不是直接使用createElement()appendChild()等DOM方法。
    设计/测试jQuery以最小化这些事情。

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

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

    4. 始终检查返回值并假设元素可能丢失:
      这是(唉,典型的):

      $("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;
      

      见下文。

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

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

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

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

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

    10. Additional reading on JS memory leaks

    11. Mozilla Performance: Leak Tools