IE 7/8/9和FF 4中的jQuery内存泄漏

lsc*_*lt2 6 jquery memory-leaks internet-explorer-9 firefox4

我一直在使用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>
Run Code Online (Sandbox Code Playgroud)

小智 -1

也许 1 个问题可能是 .live 的使用。我不确定 .live 内部是如何工作的,但它必须侦听更改 DOM 的事件(每次更新/替换 td 中的元素时,如果有新元素,<td class="w">它会使用给定的回调来观察它)。如果 .live 方法尚未准备好检查$("td.w")DOM 中包含的另一个方法,并且您开始另一次检查,则可能会导致内存泄漏。我只是猜测,但尝试替换此代码:

 $('tr.w').live('click', function(event){
   var jrow = $(this);
   if (!jrow.find('.inedBottle').length) {
    createInlineEdit(jrow);
   }
 });
Run Code Online (Sandbox Code Playgroud)

有了这个:

 $('tr.w').bind('click', function(event){
   var jrow = $(this);
   if (!jrow.find('.inedBottle').length) {
    createInlineEdit(jrow);
   }
 });
Run Code Online (Sandbox Code Playgroud)

在繁重的 DOM 操作过程中,您的内存使用率可能会越来越低。让我知道这是否有帮助!

  • 我之前实际上使用过 .bind,发现这更有可能导致内存泄漏,因为绑定事件处理程序可以创建循环引用。jQuery 建议使用 .live 而不是 .bind,因为 .live 应该可以正确处理该问题。[链接](http://stackoverflow.com/questions/2411619/browser-memory-increasing-constantly-with-javascript-ajax-calls) [链接](http://stackoverflow.com/questions/2155792/ajax-内存泄漏) (2认同)