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,要花3分钟才能稳定到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\">\',\'</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\',\'\" 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>
    

解决方法

        我最近学到的技术是,不要将事件绑定到要单击的每个对象,而是将单击绑定到页面,然后将事件查看到已单击的对象。 由于多种原因,这要快得多: 您的浏览器可以通过的绑定规则更少(较旧的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);
    }
});
这是我的头上。有一个比检查班级更好的方法,但是我有点不舒服。 希望对您有所帮助!     ,        也许1个问题可能是.live的使用。我不确定.live在内部如何工作,但是它必须侦听更改DOM的事件(每次更新/替换td \中的元素时,如果有新的
<td class=\"w\">
,它都会观察到给定的回调)。 如果.live方法尚未准备好检查DOM中包含的另一个“ 3”,而您又开始进行另一次检查,则可能导致这些内存泄漏。我只是在猜测,但请尝试替换以下代码:
 $(\'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操作期间,您的内存使用量可能会降低。让我知道是否有帮助!