这是一个死简单的网页,它使用jQuery泄漏IE8中的内存(我通过观察我的iexplore.exe进程的内存使用量在Windows任务管理器中随着时间的推移而检测到内存泄漏):
<html>
<head>
<title>Test Page</title>
<script type="text/javascript" src="jquery.js"></script>
</head>
<body>
<script type="text/javascript">
function resetContent() {
$("#content div").remove();
for(var i=0; i<10000; i++) {
$("#content").append("<div>Hello World!</div>");
}
setTimeout(resetTable, 2000);
}
$(resetContent);
</script>
<div id="content"></div>
</body>
</html>
显然即使在调用jQuery.remove()
函数时,我仍然会遇到一些内存泄漏。我可以编写自己的删除函数,不会发生内存泄漏,如下所示:
$.fn.removeWithoutLeaking = function() {
this.each(function(i,e){
if( e.parentNode )
e.parentNode.removeChild(e);
});
};
这很好用,不泄漏任何内存。那为什么jQuery会泄漏内存呢?我基于jQuery.remove()
创建了另一个删除函数,这确实会导致泄漏:
$.fn.removeWithLeakage = function() {
this.each(function(i,e) {
$("*", e).add([e]).each(function(){
$.event.remove(this);
$.removeData(this);
});
if (e.parentNode)
e.parentNode.removeChild(e);
});
};
有趣的是,内存泄漏似乎是由jQuery包含的每个调用引起的,以防止内存泄漏事件和与删除的DOM元素相关的数据。当我调用removeWithoutLeaking
函数时,我的记忆会随着时间的推移而保持不变,但是当我调用removeWithLeakage
时,它会继续增长。
我的问题是,每个电话怎么样
$("*", e).add([e]).each(function(){
$.event.remove(this);
$.removeData(this);
});
可能导致内存泄漏?
编辑:修复了代码中的拼写错误,经过重新测试,证明对结果没有影响。
进一步编辑:我已经向jQuery项目提交了一个错误报告,因为这似乎是一个jQuery错误:http://dev.jquery.com/ticket/5285
答案 0 :(得分:57)
我认为大卫可能会因涉嫌removeChild漏洞而陷入困境,但我无法在IE8中重现它...它可能会在早期的浏览器中发生,但这不是我们在这里所拥有的。如果我手动removeChild divs没有泄漏;如果我改变jQuery使用outerHTML= ''
(或者移动到bin然后是bin.innerHTML)而不是removeChild,那么仍然存在泄漏。
在消除过程中,我开始在jQuery中攻击remove
的位。 1.3.2中的第1244行:
//jQuery.event.remove(this);
jQuery.removeData(this);
评论该行没有泄漏。
所以,让我们看看event.remove,它调用data('events')
来查看是否有任何附加到元素的事件。什么是data
在做什么?
// Compute a unique ID for the element
if ( !id )
id = elem[ expando ] = ++uuid;
喔。因此,它为每个甚至尝试读取数据的元素添加了一个jQuery的uuid-to-data-lookup入口hack属性,其中包括你要移除的元素的每个后代!真傻。我可以通过在它之前放置这条线来短路:
// Don't create ID/lookup if we're only reading non-present data
if (!id && data===undefined)
return undefined;
似乎在IE8中修复了此案例的泄漏。不能保证它不会破坏jQuery迷宫中的其他东西,但逻辑上它是有道理的。
据我所知,泄漏只是jQuery.cache
对象(这是数据存储,而不是真正的缓存)随着为每个删除的新密钥添加而变得越来越大元件。尽管removeData应该删除那些缓存条目,但是当您delete
来自对象的密钥时,IE似乎不会恢复空间。
(无论哪种方式,这是我不理解的jQuery行为的一个例子。它在做什么应该是一个简单的简单操作的内幕做得太多了......其中一些是相当可疑的东西expando和what jQuery does to innerHTML
via regex to prevent that showing as an attribute in IE的整个事情只是破碎和丑陋。而使得getter和setter具有相同功能的习惯令人困惑,并且在这里导致了bug。)
[奇怪的是,在内存实际耗尽之前,在jquery.js中偶尔发出完全错误的错误会导致漏洞检测结束......有些事情就像'意外命令',我注意到'nodeName是null或不是第667行的对象,据我所知,甚至不应该运行,更不用说有一个检查nodeName为null! IE在这里没有给我很多信心......]
答案 1 :(得分:5)
似乎在jQuery 1。5(23。2月版)中修复。我遇到了与1.4.2相同的问题并首先修复了dom,如上所述,然后尝试新版本。
答案 2 :(得分:4)
元素删除是一个固有的DOM问题。哪个会留在我们身边。同上。
jQuery.fn.flush = function()
/// <summary>
/// $().flush() re-makes the current element stack inside $()
/// thus flushing-out the non-referenced elements
/// left inside after numerous remove's, append's etc ...
/// </summary>
{ return jQuery(this.context).find(this.selector); }
我使用此扩展名而不是黑客jQ。特别是在包含很多remove()和clone()的页面中:
$exact = $("whatever").append("complex html").remove().flush().clone();
下一个也有帮助:
// remove all event bindings ,
// and the jQ data made for jQ event handling
jQuery.unbindall = function () { jQuery('*').unbind(); }
//
$(document).unload(function() {
jQuery.unbindall();
});
答案 3 :(得分:2)
请参阅http://docs.jquery.com/JQuery_1.4_Roadmap处的jQuery 1.4路线图。具体来说,“使用.outerHTML清除.remove()之后”部分处理由于调用remove函数而在IE中发生的内存泄漏问题。
也许您的问题将在下一个版本中得到解决。
答案 4 :(得分:1)
如果您拨打empty
而不是remove
,它仍会泄漏吗?
$("#content").empty();
答案 5 :(得分:1)
JQuery 1.4.1有以下内容:
cleanData: function (elems) {
for (var i = 0, elem, id; (elem = elems[i]) != null; i++) {
jQuery.event.remove(elem);
jQuery.removeData(elem);
}
}
以下是我必须修改以消除泄漏问题:
cleanData: function (elems) {
for (var i = 0, elem, id; (elem = elems[i]) != null; i++) {
jQuery.event.remove(elem);
jQuery.removeData(elem);
jQuery.purge(elem);
}
}
添加功能:
purge: function (d) {
var a = d.childNodes;
if (a) {
var remove = false;
while (!remove) {
var l = a.length;
for (i = 0; i < l; i += 1) {
var child = a[i];
if (child.childNodes.length == 0) {
jQuery.event.remove(child);
d.removeChild(child);
remove = true;
break;
}
else {
jQuery.purge(child);
}
}
if (remove) {
remove = false;
} else {
break;
}
}
}
},