【问题标题】:Memory leaks with jQuery in IE 7/8/9 and FF 4IE 7/8/9 和 FF 4 中的 jQuery 内存泄漏
【发布时间】:2011-09-13 11:25:00
【问题描述】:

我一直在努力解决在所有主要浏览器中使用 jQuery 的一些内存泄漏问题,我正在寻求一些帮助。我读过很多关于内存泄漏、引用、循环引用、闭包等的文章。我认为我做的一切都是正确的。但是我仍然看到 IE9 和 FF4 中的内存增加以及 sIEve 中的孤儿,这对我来说没有意义。

我创建了一个测试用例来展示这个问题。测试用例基本上有一个500行的大表,用户可以点击每一行进入内联编辑模式,使用jQuery追加元素。当用户退出内联编辑模式时,元素会被移除。

测试用例有一个按钮来模拟 100 行的 100 次点击,以快速放大问题。

当我在 sIEve 中运行它时,内存增加了 1600KB,使用中增加了 506,并且有 99 个孤儿。有趣的是,如果我将第 123 行的 .remove() 注释掉,内存增加了 1030KB,in use 增加了 11,并且有 0 个孤儿。

当我在 IE9 中运行它时,内存增加了 5900KB。一次刷新又增加了 1500KB,而另一次运行又增加了 1K。继续这种模式会继续增加内存使用量

当我在 FF4 中运行它时,如果我使用“100 clicks slow”和“100 clicks fast”,我会得到非常不同的行为。模拟慢速点击的峰值增加了 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>

【问题讨论】:

  • 你知道大多数浏览器都使用内存池吧?我不知道你是如何进行分析的,但如果你只是使用任务管理器中的“内存使用”列,它不会太准确。大多数浏览器会分配内存,然后在内部进行分配。即使内存没有被使用,它仍然会被列为“正在使用”。
  • 另外,您看到的 1KB 增加可能只不过是缓存机制。
  • @Chris:我有点熟悉,但与浏览器如何使用内存的专家相去甚远。我还在学习,这是我的问题的一部分。我在 Windows 7 的资源监视器中使用“工作集”列,它应该与任务管理器的“内存(私有工作集)”列加上可共享内存相同。我不知道缓存是什么。当我单击行时,页面并没有被导航:它只是动态添加输入元素并删除它们。 IE9 或 FF4 会在一个网页中缓存任何此类活动吗?这不是我的理解。
  • @lschult2:是的,工作集只是为流程保留的数量。因此,假设您的脚本使用 10MB(只是为了好玩)。它会要求 IE 10MB。 IE 会检查自己的内存池。我们假设它没有 10MB 可用空间。它可能会转向操作系统并要求,哦,比如说,50MB。然后它会转过来将 10MB 交给您的脚本,并保留其他 40 个以供以后使用。资源管理器将显示 50MB 正在使用中。当你的脚本完成后,10 将被放回,但 IE 看​​起来仍然使用 50。看起来像泄漏,但不是。
  • 关于缓存的猜测只是猜测。它可能是各种各样的东西。历史,也许?据我所知,您可能已经发现了一个完全合法的泄漏。

标签: jquery memory-leaks internet-explorer-9 firefox4


【解决方案1】:

我最近学到的技术不是将事件绑定到您想要单击的每个对象,而是将单击绑定到页面并查看事件与已单击的对象。

由于多种原因,这要快得多:

  • 您的浏览器要通过的绑定规则要少得多(旧的 FF 和 IE 处理能力很差,您可能不会在 Opera 或 Chrome 上注意到这些问题)
  • 整个过程要快得多。

例子:

$(body).click( function (event) {
    $target = $(event.target);
    if ( $target.hasClass('w') ) {
        var jrow = $target;
        if (!jrow.find('.inedBottle').length) {
        createInlineEdit(jrow);
    }
});

这不是我的想法。可能有比查看课程更好的方法,但我有点不舒服。

希望对你有帮助!

【讨论】:

    【解决方案2】:

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

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

    用这个:

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

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

    【讨论】:

    • 我之前实际使用过.bind,发现它更容易导致内存泄漏,因为绑定的事件处理程序可以创建循环引用。 jQuery 推荐 .live 而不是 .bind,因为 .live 应该能够正确处理。 linklink
    • 我不知道这对于已经在 DOM 中并且不受替换影响的元素是否正确...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-01
    • 1970-01-01
    相关资源
    最近更新 更多