删除DOM时jQuery内存泄漏

时间:2009-09-22 21:15:01

标签: jquery memory-leaks

这是一个死简单的网页,它使用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

6 个答案:

答案 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;
                }
            }
        }
    },