IE 7/8/9 和 FF 4 中 jQuery 的内存泄漏
我一直在所有主要浏览器中使用 jQuery 时遇到一些内存泄漏问题,并且正在寻求一些帮助。我读过很多关于内存泄漏、引用、循环引用、闭包等的文章。我认为我做的一切都是正确的。但我仍然看到 IE9 和 FF4 中的内存增加以及 sIEve 中的孤儿,这对我来说没有意义。
我创建了一个测试用例来展示该问题。测试用例基本上有一个 500 行的大表,用户可以单击每一行进入内联编辑模式,其中使用 jQuery 附加元素。当用户退出内联编辑模式时,元素将被删除。
测试用例有一个按钮可以模拟 100 行的 100 次点击,以快速放大问题。
当我在 sIEve 中运行它时,内存增加了 1600KB,使用中增加了 506,并且有 99 个孤儿。有趣的是,如果我注释掉第 123 行上的 .remove(),内存会增加 1030KB,使用中会增加 11,并且有 0 个孤儿。
当我在IE9中运行它时,内存增加了5900KB。刷新会增加 1500KB,另一次运行会增加 1K。继续这种模式会继续增加内存使用量
当我在 FF4 中运行它时,如果我使用“慢速 100 次点击”和“快速 100 次点击”,我会得到非常不同的行为。模拟慢速点击的峰值增加了 8300KB,需要一分钟才能稳定到 3300KB。模拟快速点击峰值增加了 27,700KB,然后需要一分钟才稳定到 4700KB。请注意,这与执行的代码完全相同,只是执行之间的延迟较少。刷新和另一次运行会继续以相似的速度增加内存。
示例代码:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Strict//EN">
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script type="text/javascript">
var clickcounter = 0;
var clickdelay = 0;
var clickstart = 0;
var clicklimit = 0;
$(document).ready(function(){
// Create a table with 500 rows
var tmp = ['<table>'];
for (var i = 0; i < 500; i++) {
tmp.push('<tr id="product_id',i+1,'" class="w editable">');
tmp.push('<td class="bin">',i+1,'</td>');
tmp.push('<td class="productcell" colspan="2">Sample Product Name<div class="desc"></div></td>');
tmp.push('<td class="percentcost">28%</td>');
tmp.push('<td class="cost">22.50</td>');
tmp.push('<td class="quantity">12</td>');
tmp.push('<td class="status">Active</td>');
tmp.push('<td class="glass">23</td>');
tmp.push('<td class="bottle">81</td>');
tmp.push('</tr>');
}
tmp.push('</table>');
$('body').append(tmp.join(''));
// Live bind a click event handler to enter Inline Edit
$('tr.w').live('click', function(event){
var jrow = $(this);
if (!jrow.find('.inedBottle').length) {
createInlineEdit(jrow);
}
});
// This is just to emulate 100 clicks on consecutive rows
$('#slow100').click(function() {
clickstart = clickcounter;
clickcounter++;
var jrow = $('#product_id'+clickcounter);
createInlineEdit(jrow);
clickdelay = 1000;
clicklimit = 100;
window.setTimeout(clickemulate, clickdelay);
});
// This is just to emulate 100 rapid clicks on consecutive rows
$('#fast100').click(function() {
clickstart = clickcounter;
clickcounter++;
var jrow = $('#product_id'+clickcounter);
createInlineEdit(jrow);
clickdelay = 20;
clicklimit = 100;
window.setTimeout(clickemulate, clickdelay);
});
});
// Emulate clicking on the next row and waiting the delay period to click on the next
function clickemulate() {
if ((clickcounter - clickstart) % clicklimit == 0) return;
nextInlineEdit($('#product_id'+ clickcounter));
clickcounter++;
window.setTimeout(clickemulate, clickdelay);
}
// Enter inline edit mode for the row
function createInlineEdit(jrow, lastjrow) {
removeInlineEdit(lastjrow);
jrow.removeClass('editable').addClass('editing');
// Find each of the cells
var productcell = jrow.find('.productcell');
var bincell = jrow.find('.bin');
var percentcostcell = jrow.find('.percentcost');
var costcell = jrow.find('.cost');
var glasscell = jrow.find('.glass');
var bottlecell = jrow.find('.bottle');
var descdiv = productcell.find('.desc');
var product_id = jrow.attr('id').replace(/^product_id/,'');
// Replace with an input
bincell.html('<input class="inedBin" name="bin'+product_id+'" value="'+bincell.text()+'">');
costcell.html('<input class="inedCost" name="cost'+product_id+'" value="'+costcell.text()+'">');
glasscell.html('<input class="inedGlass" name="glass'+product_id+'" value="'+glasscell.text()+'">');
bottlecell.html('<input class="inedBottle" name="bottle'+product_id+'" value="'+bottlecell.text()+'">');
var tmp = [];
// For one input, insert a few divs and spans as well as the inputs.
// Note: the div.ined and the spans and input underneath are the ones remaining as orphans in sIEve
tmp.push('<div class="ined">');
tmp.push('<span>Inserted Span 1</span>');
tmp.push('<span>Inserted Span 2</span>');
tmp.push('<input class="inedVintage" name="vintage',product_id,'" value="">');
tmp.push('<input class="inedSize" name="size',product_id,'" value="">');
tmp.push('</div>');
tmp.push('<div class="descinner">');
tmp.push('<input class="inedDesc" name="desc'+product_id+'" value="'+descdiv.text()+'">');
tmp.push('</div>');
descdiv.html(tmp.join(''));
jrow.find('.inedVintage').focus().select();
}
// Exit the inline edit mode
function removeInlineEdit(jrow) {
if (jrow && jrow.length) {
} else {
jrow = $('tr.w.editing');
}
jrow.removeClass('editing').addClass('editable');
// Note: the div.ined and the spans and input underneath are the ones remaining as orphans in sIEve
// sIEve steps: load page, click "Clear in use", click "100 clicks fast" on the page
// If the remove is commented out, then sIEve does not report any div.ined as orphans and reports 11 in use (div.ined all appear to be garbage collected)
// If the remove is uncommented, then sIEve reports 99 of the div.ined as orphans and reports 506 in use (none of the div.ined garbage collected)
jrow.find('.ined').remove();
jrow.find('.inedBin').each(function() {
$(this).replaceWith(this.defaultValue);
});
jrow.find('.inedGlass').each(function() {
$(this).replaceWith(this.defaultValue);
});
jrow.find('.inedBottle').each(function() {
$(this).replaceWith(this.defaultValue);
});
jrow.find('.inedCost').each(function() {
$(this).replaceWith(this.defaultValue);
});
jrow.find('.inedDesc').each(function() {
// Since the div.ined is under here, this also removes it.
$(this).closest('.desc').html(this.defaultValue);
});
}
function nextInlineEdit(jrow) {
var nextjrow = jrow.nextAll('tr.w').first();
if (nextjrow.length) {
createInlineEdit(nextjrow, jrow);
} else {
removeInlineEdit(jrow);
}
}
</script>
<style>
table {margin-top: 30px;}
td {border: 1px dashed grey;}
button#slow100 {position: fixed; left: 0px; width: 115px;}
button#fast100 {position: fixed; left: 120px; width: 115px;}
</style>
</head>
<body>
<button id="slow100">100 clicks slow</button>
<button id="fast100">100 clicks fast</button>
</body>
</html>
I have been struggling with some memory leaks using jQuery in all major browsers, and am looking for some help. I've read many articles on memory leaks, references, circular references, closures, etc. I think I'm doing everything right. But am still seeing memory increases in IE9 and FF4 and orphans in sIEve that just don't make sense to me.
I've created a test case to showcase the issue. The test case basically has a large table of 500 rows, and users can click on each row to enter an inline edit mode where elements are appended using jQuery. When users exit inline edit mode, the elements are removed.
The test case has a button to emulate 100 clicks for 100 rows to quickly magnify the issue.
When I run it in sIEve, memory increases 1600KB, in use goes up by 506, and there are 99 orphans. Interestingly, if I comment out the .remove() on line 123, memory increase 1030KB, in use goes up by 11, and there are 0 orphans.
When I run it in IE9, memory increase 5900KB. A refresh increases another 1500KB and another run increases anotherr 1K. Continuing this pattern continues the memory use increase
When I run it in FF4, I get very different behavior if I use "100 clicks slow" and "100 clicks fast". Emulating slow clicks has a peak increase of 8300KB and it takes a minute for it to settle down to 3300KB. Emulating fast clicks has a peak increase of 27,700KB, then it takes a minute to settle down to 4700KB. Note this is the exact same code that is executed, just with less delays between executing. A refresh and another run continues to increase memory at a similar rate.
Sample code:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Strict//EN">
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script type="text/javascript">
var clickcounter = 0;
var clickdelay = 0;
var clickstart = 0;
var clicklimit = 0;
$(document).ready(function(){
// Create a table with 500 rows
var tmp = ['<table>'];
for (var i = 0; i < 500; i++) {
tmp.push('<tr id="product_id',i+1,'" class="w editable">');
tmp.push('<td class="bin">',i+1,'</td>');
tmp.push('<td class="productcell" colspan="2">Sample Product Name<div class="desc"></div></td>');
tmp.push('<td class="percentcost">28%</td>');
tmp.push('<td class="cost">22.50</td>');
tmp.push('<td class="quantity">12</td>');
tmp.push('<td class="status">Active</td>');
tmp.push('<td class="glass">23</td>');
tmp.push('<td class="bottle">81</td>');
tmp.push('</tr>');
}
tmp.push('</table>');
$('body').append(tmp.join(''));
// Live bind a click event handler to enter Inline Edit
$('tr.w').live('click', function(event){
var jrow = $(this);
if (!jrow.find('.inedBottle').length) {
createInlineEdit(jrow);
}
});
// This is just to emulate 100 clicks on consecutive rows
$('#slow100').click(function() {
clickstart = clickcounter;
clickcounter++;
var jrow = $('#product_id'+clickcounter);
createInlineEdit(jrow);
clickdelay = 1000;
clicklimit = 100;
window.setTimeout(clickemulate, clickdelay);
});
// This is just to emulate 100 rapid clicks on consecutive rows
$('#fast100').click(function() {
clickstart = clickcounter;
clickcounter++;
var jrow = $('#product_id'+clickcounter);
createInlineEdit(jrow);
clickdelay = 20;
clicklimit = 100;
window.setTimeout(clickemulate, clickdelay);
});
});
// Emulate clicking on the next row and waiting the delay period to click on the next
function clickemulate() {
if ((clickcounter - clickstart) % clicklimit == 0) return;
nextInlineEdit($('#product_id'+ clickcounter));
clickcounter++;
window.setTimeout(clickemulate, clickdelay);
}
// Enter inline edit mode for the row
function createInlineEdit(jrow, lastjrow) {
removeInlineEdit(lastjrow);
jrow.removeClass('editable').addClass('editing');
// Find each of the cells
var productcell = jrow.find('.productcell');
var bincell = jrow.find('.bin');
var percentcostcell = jrow.find('.percentcost');
var costcell = jrow.find('.cost');
var glasscell = jrow.find('.glass');
var bottlecell = jrow.find('.bottle');
var descdiv = productcell.find('.desc');
var product_id = jrow.attr('id').replace(/^product_id/,'');
// Replace with an input
bincell.html('<input class="inedBin" name="bin'+product_id+'" value="'+bincell.text()+'">');
costcell.html('<input class="inedCost" name="cost'+product_id+'" value="'+costcell.text()+'">');
glasscell.html('<input class="inedGlass" name="glass'+product_id+'" value="'+glasscell.text()+'">');
bottlecell.html('<input class="inedBottle" name="bottle'+product_id+'" value="'+bottlecell.text()+'">');
var tmp = [];
// For one input, insert a few divs and spans as well as the inputs.
// Note: the div.ined and the spans and input underneath are the ones remaining as orphans in sIEve
tmp.push('<div class="ined">');
tmp.push('<span>Inserted Span 1</span>');
tmp.push('<span>Inserted Span 2</span>');
tmp.push('<input class="inedVintage" name="vintage',product_id,'" value="">');
tmp.push('<input class="inedSize" name="size',product_id,'" value="">');
tmp.push('</div>');
tmp.push('<div class="descinner">');
tmp.push('<input class="inedDesc" name="desc'+product_id+'" value="'+descdiv.text()+'">');
tmp.push('</div>');
descdiv.html(tmp.join(''));
jrow.find('.inedVintage').focus().select();
}
// Exit the inline edit mode
function removeInlineEdit(jrow) {
if (jrow && jrow.length) {
} else {
jrow = $('tr.w.editing');
}
jrow.removeClass('editing').addClass('editable');
// Note: the div.ined and the spans and input underneath are the ones remaining as orphans in sIEve
// sIEve steps: load page, click "Clear in use", click "100 clicks fast" on the page
// If the remove is commented out, then sIEve does not report any div.ined as orphans and reports 11 in use (div.ined all appear to be garbage collected)
// If the remove is uncommented, then sIEve reports 99 of the div.ined as orphans and reports 506 in use (none of the div.ined garbage collected)
jrow.find('.ined').remove();
jrow.find('.inedBin').each(function() {
$(this).replaceWith(this.defaultValue);
});
jrow.find('.inedGlass').each(function() {
$(this).replaceWith(this.defaultValue);
});
jrow.find('.inedBottle').each(function() {
$(this).replaceWith(this.defaultValue);
});
jrow.find('.inedCost').each(function() {
$(this).replaceWith(this.defaultValue);
});
jrow.find('.inedDesc').each(function() {
// Since the div.ined is under here, this also removes it.
$(this).closest('.desc').html(this.defaultValue);
});
}
function nextInlineEdit(jrow) {
var nextjrow = jrow.nextAll('tr.w').first();
if (nextjrow.length) {
createInlineEdit(nextjrow, jrow);
} else {
removeInlineEdit(jrow);
}
}
</script>
<style>
table {margin-top: 30px;}
td {border: 1px dashed grey;}
button#slow100 {position: fixed; left: 0px; width: 115px;}
button#fast100 {position: fixed; left: 120px; width: 115px;}
</style>
</head>
<body>
<button id="slow100">100 clicks slow</button>
<button id="fast100">100 clicks fast</button>
</body>
</html>
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我最近学到的技术是将事件绑定到您想要单击的每个对象,而是将一次单击绑定到页面并查看事件到已单击的对象。
由于多种原因,这种速度要快得多:
示例:
这超出了我的想象。可能有比检查课程更好的方法,但我有点不舒服。
我希望它有帮助!
I recent technique I've learnt is to instead of binding events to every object you want clicked, bind one click to the page and look at the event to what object has been clicked.
This is much faster for multiple reasons:
Example:
this is off of the top of my head. There is prob a better way than checking the class but I'm a little under the weather.
I hope it helps!
也许 1 个问题可能是 .live 的使用。我不确定 .live 内部如何工作,但它必须侦听更改 DOM 的事件(每次更新/替换 td 中的元素时,以及是否有新的
它通过给定的回调来观察它)。
如果 .live 方法尚未准备好检查 DOM 中包含的另一个
$("td.w")
并且您开始另一次检查,则可能会导致内存泄漏。我只是猜测,但尝试将此代码替换为
:在繁重的 DOM 操作期间,也许您的内存使用量会降低。让我知道这是否有帮助!
Maybe 1 problem could be the use of .live. I'm not sure how .live works internally, but it must listen to an event that changes the DOM (every time you update/replace the elements in your td's, and if there is a new
<td class="w">
it observes it with the given callback).If the .live method is not ready checking for another
$("td.w")
included to the DOM and you start another check, it can cause those memory leaks. I'm just guessing, but try to replace this code:with this:
and maybe your memory usage is getting lower, during the heavy DOM operations. Let me know if this helps!