我有一个GreaseMonkey脚本,适用于使用框架作为其界面不可分割的一部分的网站。这个脚本像筛子一样泄漏内存,我相信这是因为我在其中一个框架中使用了addEventListener。很简单,我附加了各种事件监听器,然后框架重新加载并附加事件监听器,然后当您与此框架中的各种元素或其他元素交互时,框架会重新加载数周或数千次迭代。到最后,Firefox已经从大约300万内存增加到2G(或者在它到达之前崩溃)。
我在某处读到,执行整页重新加载将允许FireFox的垃圾收集例程启动并从孤立的事件处理程序中恢复所有内存,当我在脚本运行一段时间后,在大约10秒内点击F5时就足够了秒内存减少到300M。不幸的是,这打破了网站中的一个不同的框架(一个非常流行的聊天窗口),所以虽然它似乎证实了我怀疑addEventListener应该受到责备,但它并不是一个真正的选择作为解决方案。
在不强制整页刷新的情况下,我还能做些什么来正确释放内存吗?
(目前使用的是GM 1.5和FF 17,但问题是自GM 0.8 / FF 4以后就存在这个问题。)
答案 0 :(得分:9)
如果没有看到完整的脚本或Short, Self Contained, Compilable Example,我们就无法确定发生了什么。可能addEventListener
不是问题。
以下是一些更好的代码策略,内存泄漏更少:
内联/匿名函数 通常是罪魁祸首,特别是对于事件处理程序。
贫穷/漏洞:
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.
同样,不要在HTML属性上使用JS。 EG不要使用<span onclick="callSomeFunction()">
等
将iframe中运行的代码最小化为您明确需要的代码。
@include
,@exclude
和@match
指令阻止尽可能多的不需要的iframe。Wrap all code that doesn't need to run in iframes in a block喜欢这样:
if (window.top === window.self) {
// Not in a frame
}
请勿使用innerHTML
。
对于许多元素或使用AJAX来的元素,请不要使用addEventListener()
或jQuery的.bind()
,.click()
等。
这会将侦听器复制到数千个节点上。
使用jQuery's .on()
。这样,监听器只连接一次并通过冒泡适当地触发。 (请注意,在某些罕见案例中,.on()
可以被页面的javascript阻止。)
在您的情况下,您可能需要以下内容:
$(document).on ("click", "YOUR ELEM SELECTOR", clickHandler);
function clickHandler (evt) {
/*do something*/
}
为避免出现意外的循环引用或孤立项,请使用jQuery添加或删除元素,而不是直接使用createElement()
,appendChild()
等DOM方法。
设计/测试jQuery以最小化这些事情。
谨防过度使用GM_setValue()
。它很容易使用大量全局资源或导致脚本实例崩溃。
localStorage
。GM_setValue()
存储除字符串之外的任何内容。对于其他任何事情,请使用序列化程序,例如GM_SuperValue
。即使看起来无辜的整数也会导致默认GM_setValue()
崩溃。
始终检查返回值并假设元素可能丢失:
这是差(唉,典型的):
$("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;
见下文。
谨防递归/内联setTimeout()
来电。使用setInterval()
进行重复计时。就像使用事件处理程序一样,不要使用内联/匿名函数。
通过JSLint运行您的代码。
避免使用eval()
和auto/hidden eval()
invocations。
完成后,将变量设置为null
。 See this, for example.