3

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

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

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

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

4

1 回答 1

10

如果没有看到您的完整脚本或简短的、自包含的、可编译的示例,我们无法确定发生了什么。这可能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*/
    }
    

    请注意,对于用户脚本,无论如何您都应该避免使用onclick,等等。

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

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

    1. 使用@include@exclude@match指令尽可能多地阻止不需要的 iframe。
    2. 将不需要在 iframe 中运行的所有代码包装在一个块中,如下所示:

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

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

    使用jQuery 的.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()自动/隐藏eval()调用

  12. 将变量设置null为完成它们的时间。例如,请参见此。

  13. 参考:“你知道什么可能导致 JavaScript 中的内存泄漏吗?”

  14. 关于 JS 内存泄漏的补充阅读

  15. Mozilla性能:泄漏工具

于 2012-12-04T12:21:30.787 回答