移除 DOM 后的 jQuery 内存泄漏 [英] jQuery memory leak with DOM removal

查看:69
本文介绍了移除 DOM 后的 jQuery 内存泄漏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是一个非常简单的网页,它使用 jQuery 在 IE8 中泄漏内存(我通过在 Windows 任务管理器中观察 iexplore.exe 进程的内存使用量随时间增长来检测内存泄漏):

<头><title>测试页</title><script type="text/javascript" src="jquery.js"></script><身体><script type="text/javascript">功能重置内容(){$("#content div").remove();for(var i=0; i<10000; i++) {$("#content").append("

Hello World!

");}设置超时(重置表,2000);}$(重置内容);<div id="内容"></div>

显然,即使在调用 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);});如果(e.parentNode)e.parentNode.removeChild(e);});};

有趣的是,内存泄漏似乎是由 jQuery 包含的 each 调用引起的,以防止与正在删除的 DOM 元素相关的事件和数据导致内存泄漏.当我调用 removeWithoutLeaking 函数时,我的记忆会随着时间的推移保持不变,但是当我调用 removeWithLeakage 时,它只会不断增长.

我的问题是,每次调用都怎么样

$("*", e).add([e]).each(function(){$.event.remove(this);$.removeData(this);});

可能导致内存泄漏?

修正了代码中的错字,经重新测试,证明对结果没有影响.

进一步我已经向 jQuery 项目提交了一个错误报告,因为这似乎是一个 jQuery 错误:http://dev.jquery.com/ticket/5285

解决方案

我认为 David 可能对所谓的 removeChild 泄漏有所了解,但我无法在 IE8 中重现它......它很可能发生在早期的浏览器中,但这不是我们在这里所拥有的.如果我手动 removeChild div 没有泄漏;如果我更改 jQuery 以使用 outerHTML=''(或 move-to-bin 后跟 bin.innerHTML)而不是 removeChild,则仍然存在泄漏.

在消除过程中,我开始在 jQuery 中对 remove 进行一些修改.1.3.2 中的第 1244 行:

//jQuery.event.remove(this);jQuery.removeData(this);

注释掉该行不会导致泄漏.

那么,让我们看看 event.remove,它调用 data('events') 来查看是否有任何附加到元素的事件.data 在做什么?

//计算元素的唯一 ID如果(!id)id = elem[ expando ] = ++uuid;

哦.因此,它为它甚至尝试读取数据的每个元素添加了 jQuery 的 uuid-to-data-lookup 条目 hack 属性之一,其中包括您要删除的元素的每个后代!多么愚蠢.我可以通过将这条线放在它之前来短路它:

//如果我们只读取不存在的数据,则不要创建 ID/查找if (!id && 数据===未定义)返回未定义;

这似乎修复了 IE8 中这种情况下的泄漏.不能保证它不会破坏 jQuery 迷宫中的其他东西,但从逻辑上讲它是有道理的.

据我所知,泄漏只是 jQuery.cache 对象(它是数据存储,而不是真正的缓存)作为新键变得越来越大为每个删除的元素添加.尽管 removeData 应该可以删除这些缓存条目,但是当您从对象中删除一个键时,IE 似乎没有恢复空间.

(无论哪种方式,这是我不喜欢的那种 jQuery 行为的一个例子.它在引擎盖下做了太多的事情,这应该是一个简单的操作......其中一些是非常值得怀疑的东西. expando 的全部内容以及 jQuery 对 innerHTML 通过正则表达式来防止在 IE 中显示为一个属性 只是破坏和丑陋.使 getter 和 setter 具有相同功能的习惯令人困惑,并在这里导致错误.)

[奇怪的是,在内存实际耗尽之前,长时间离开泄漏测试最终会在 jquery.js 中给出完全虚假的错误......null or not an object' 在第 667 行,据我所知,它甚至不应该运行,更不用说那里检查 nodeName 是否为 null!IE在这里并没有给我太多信心...]

Here's a dead-simple webpage that leaks memory in IE8 using jQuery (I detect memory leaks by watching the memory usage of my iexplore.exe process grow over time in the Windows Task Manager):

<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>

Apparently even when calling the jQuery.remove() function I still experience some memory leakage. I can write my own remove function that experiences no memory leak as follows:

$.fn.removeWithoutLeaking = function() {
    this.each(function(i,e){
        if( e.parentNode )
            e.parentNode.removeChild(e);
    });
};

This works just fine and doesn't leak any memory. So why does jQuery leak memory? I created another remove function based on jQuery.remove() and this does indeed cause a leak:

$.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);
    });
};

Interestingly, the memory leak seems to be caused by the each call which jQuery includes to prevent memory leaks from events and data associated with the DOM elements being deleted. When I call the removeWithoutLeaking function then my memory stays constant over time, but when I call removeWithLeakage instead then it just keeps growing.

My question is, what about that each call

$("*", e).add([e]).each(function(){
    $.event.remove(this);
    $.removeData(this);
});

could possibly be causing the memory leak?

EDIT: Fixed typo in code which, upon retesting, proved to have no effect on the results.

FURTHER EDIT: I have filed a bug report with the jQuery project, since this does seem to be a jQuery bug: http://dev.jquery.com/ticket/5285

解决方案

I thought David might be onto something with the alleged removeChild leak, but I can't reproduce it in IE8... it may well happen in earlier browsers, but that's not what we have here. If I manually removeChild the divs there is no leak; if I alter jQuery to use outerHTML= '' (or move-to-bin followed by bin.innerHTML) instead of removeChild there is still a leak.

In a process of elimination I started hacking at bits of remove in jQuery. line 1244 in 1.3.2:

//jQuery.event.remove(this);
jQuery.removeData(this);

Commenting out that line resulted in no leak.

So, let's look at event.remove, it calls data('events') to see if there are any events attached to the element. What is data doing?

// Compute a unique ID for the element
if ( !id )
    id = elem[ expando ] = ++uuid;

Oh. So it's adding one of jQuery's uuid-to-data-lookup entry hack properties for every element it even tries to read data on, which includes every single descendent of an element you're removing! How silly. I can short-circuit that by putting this line just before it:

// Don't create ID/lookup if we're only reading non-present data
if (!id && data===undefined)
    return undefined;

which appears to fix the leak for this case in IE8. Can't guarantee it won't break something else in the maze that is jQuery, but logically it makes sense.

As far as I can work out, the leak is simply the jQuery.cache Object (which is the data store, not a really a cache as such) getting bigger and bigger as a new key is added for every removed element. Although removeData should be removing those cache entries OK, IE does not appear to recover the space when you delete a key from an Object.

(Either way, this is an example of the sort of jQuery behaviour I don't appreciate. It is doing far too much under the hood for what should be a trivially simple operation... some of which is pretty questionable stuff. The whole thing with the expando and what jQuery does to innerHTML via regex to prevent that showing as an attribute in IE is just broken and ugly. And the habit of making the getter and setter the same function is confusing and, here, results in the bug.)

[Weirdly, leaving the leaktest for extended periods of time ended up occasionally giving totally spurious errors in jquery.js before the memory actually ran out... there was something like ‘unexpected command’, and I noted a ‘nodeName is null or not an object’ at line 667, which as far as I can see shouldn't even have been run, let alone that there is a check there for nodeName being null! IE is not giving me much confidence here...]

这篇关于移除 DOM 后的 jQuery 内存泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆