JQuery垃圾收集 - 这会干净吗?

时间:2021-08-03 03:47:30

Many articles (e.g. msdn) have said that a circular reference cannot be cleaned up in some browsers when it involves a DOM object and a JS object.

许多文章(例如msdn)已经说过,当涉及DOM对象和JS对象时,在某些浏览器中无法清除循环引用。

(IE 6 can't do it at all and IE7 can only do it between page requests):

(IE 6根本无法完成,IE7只能在页面请求之间执行):

Javascript Native (Leaks):

Javascript Native(Leaks):

function leak(){
    var elem = document.createElement("DIV");
    document.body.appendChild(elem);
    elem.onclick = function () {
        elem.innerHTML = elem.innerHTML + ".";
        // ...
    };
}

Because the element's onload property refers back to itself through a closure, it creates a circular reference:

因为元素的onload属性通过闭包引用回自身,所以它创建了一个循环引用:

elem [DOM] -> elem.onclick [JS] -> elem [DOM]

elem [DOM] - > elem.onclick [JS] - > elem [DOM]

JQuery Version (Does NOT Leak):

JQuery版本(不泄漏):

function leak(){
    var elem = $('<div></div>');
    $(document.body).append(elem);
    elem.click(function () {
        elem.html(elem.html() + ".");
        // ...
    };
}

In this case, jQuery stops the leak from happening in ALL browsers concerned even though there is still a circular reference:

在这种情况下,即使仍有循环引用,jQuery也会阻止所有相关浏览器中发生泄漏:

elem [JS] -> element [DOM] -> elem.onclick [JS] -> elem [JS]

elem [JS] - > element [DOM] - > elem.onclick [JS] - > elem [JS]

My Question: How does jQuery stop the leak if there is still a circular reference?

我的问题:如果仍有循环引用,jQuery如何阻止泄漏?

3 个解决方案

#1


6  

The very last thing in the jQuery code (before the code for Sizzle, its selector engine) is this (which is the code to prevent the leaks):

jQuery代码中的最后一件事(在Sizzle的代码之前,它的选择器引擎)是这个(这是防止泄漏的代码):

// Prevent memory leaks in IE
// Window isn't included so as not to unbind existing unload events
// More info:
//  - http://isaacschlueter.com/2006/10/msie-memory-leaks/
if ( window.attachEvent && !window.addEventListener ) {
    window.attachEvent("onunload", function() {
        for ( var id in jQuery.cache ) {
            if ( jQuery.cache[ id ].handle ) {
                // Try/Catch is to handle iframes being unloaded, see #4280
                try {
                    jQuery.event.remove( jQuery.cache[ id ].handle.elem   );
                } catch(e) {}
            }
        }
    });
}

When you do anything in jQuery, it stores both what it has done (i.e. the function) and to what (i.e. the DOM element). onunload goes through the jQuery cache removing the functions from the event handlers of its own internal cache (which is where the events are stored anyway rather than on the individual DOM nodes).

当你在jQuery中做任何事情时,它会存储它所做的事情(即函数)和什么(即DOM元素)。 onunload通过jQuery缓存从其自己的内部缓存的事件处理程序中移除函数(无论如何都存储事件而不是单个DOM节点)。

Oh, and the line:

哦,行:

if ( window.attachEvent && !window.addEventListener ) {

ensures that it just runs on IE.

确保它只在IE上运行。

#2


2  

JQuery can only ensure there aren't leaks when you do all your manipulations through the library. There are routines in jQuery called "empty" and "cleanData" that you can peruse to see exactly what's going on, but basically the code just detaches everything it knows about from DOM elements before freeing them. That routine is called when you do something like overwrite element content with ".html()" or ".load()".

JQuery只能确保在通过库进行所有操作时没有泄漏。 jQuery中有一些名为“empty”和“cleanData”的例程,你可以仔细查看它们究竟发生了什么,但基本上代码只是在释放它们之前从DOM元素中分离出它所知道的一切。当您使用“.html()”或“.load()”覆盖元素内容时,会调用该例程。

Personally I'm pretty wary of terms like "guarantee" in a situation like this.

我个人对这种情况下的“保证”等术语非常谨慎。

#3


1  

rewritten to clarify further

改写以进一步澄清

There's actually 2 causes for memory leaks in the offered example. The first memory leak manifests due to creating a direct reference to a live DOM node inside a closure. The garbage collectors (JS & DOM) of legacy browsers such as IE6 cannot nullify such references. Hence the need to null out node references at the end of your function.

在提供的示例中,实际上有两个导致内存泄漏的原因。第一个内存泄漏由于创建对闭包内的实时DOM节点的直接引用而显现。诸如IE6的传统浏览器的垃圾收集器(JS和DOM)不能使这样的引用无效。因此需要在函数末尾清空节点引用。

jQuery circumvents this by default due to live DOM elements being attached to the jQuery object as attributes/properties, with which the afore mentioned garbage collectors have no trouble in determining null references. If the jQuery object has null references it's simply cleaned up and it's attributes/properties (in this case references to live DOM elements) along with it.

由于实时DOM元素作为属性/属性附加到jQuery对象,jQuery默认绕过这个,前面提到的垃圾收集器在确定空引用时没有任何问题。如果jQuery对象具有空引用,则只需清除它,并将其属性/属性(在本例中为对实时DOM元素的引用)与其一起清除。

So in order to avoid this memory leak, is to have an object maintain the reference to the live DOM node and then reference to the object in your closures. The closures will only maintain the references to the object and not the live DOM element since that reference belongs to the object.

所以为了避免这种内存泄漏,就是让一个对象维护对live DOM节点的引用,然后引用你的闭包中的对象。闭包只会维护对象的引用而不是live DOM元素,因为该引用属于对象。

// will still leak, but not due to closure references, thats solved.
function noLeak(){
    var obj= {
        elem: document.createElement('div')
    }
    obj.elem.onclick = function(){
        obj.elem.innerHTML = obj.elem.innerHTML + ".";
    }
}

This cleared the most obvious circular reference, but there's still a leak (onclick). The node has a reference to a function which has a reference to the object which in turn has a reference to the node. The only way to circumvent this leak, is to learn from the addEvent contest (lots of people worked to solve this leak ;) ). Coincidentaly, needed code can be found therein so my appologies for not supplying code for that ;)

这清除了最明显的循环引用,但仍然存在泄漏(onclick)。该节点具有对函数的引用,该函数具有对对象的引用,该对象又具有对该节点的引用。避免这种泄漏的唯一方法是从addEvent竞赛中学习(许多人努力解决这个漏洞;))。巧合的是,所需的代码可以在其中找到,所以我的appologies没有提供代码;)

Creating a wrapper for the event system adds some more code, but is essential. The main idea is to add a common eventHandler that delegates the event to an event cache/system which stores the required references. On an unload event, the cache gets cleared breaking circular references allowing the garbage collectors (JS and DOM) to tidy up their own corners.

为事件系统创建包装器会添加更多代码,但这是必不可少的。主要思想是添加一个公共eventHandler,它将事件委托给存储所需引用的事件缓存/系统。在卸载事件中,缓存被清除,打破循环引用,允许垃圾收集器(JS和DOM)整理自己的角落。

#1


6  

The very last thing in the jQuery code (before the code for Sizzle, its selector engine) is this (which is the code to prevent the leaks):

jQuery代码中的最后一件事(在Sizzle的代码之前,它的选择器引擎)是这个(这是防止泄漏的代码):

// Prevent memory leaks in IE
// Window isn't included so as not to unbind existing unload events
// More info:
//  - http://isaacschlueter.com/2006/10/msie-memory-leaks/
if ( window.attachEvent && !window.addEventListener ) {
    window.attachEvent("onunload", function() {
        for ( var id in jQuery.cache ) {
            if ( jQuery.cache[ id ].handle ) {
                // Try/Catch is to handle iframes being unloaded, see #4280
                try {
                    jQuery.event.remove( jQuery.cache[ id ].handle.elem   );
                } catch(e) {}
            }
        }
    });
}

When you do anything in jQuery, it stores both what it has done (i.e. the function) and to what (i.e. the DOM element). onunload goes through the jQuery cache removing the functions from the event handlers of its own internal cache (which is where the events are stored anyway rather than on the individual DOM nodes).

当你在jQuery中做任何事情时,它会存储它所做的事情(即函数)和什么(即DOM元素)。 onunload通过jQuery缓存从其自己的内部缓存的事件处理程序中移除函数(无论如何都存储事件而不是单个DOM节点)。

Oh, and the line:

哦,行:

if ( window.attachEvent && !window.addEventListener ) {

ensures that it just runs on IE.

确保它只在IE上运行。

#2


2  

JQuery can only ensure there aren't leaks when you do all your manipulations through the library. There are routines in jQuery called "empty" and "cleanData" that you can peruse to see exactly what's going on, but basically the code just detaches everything it knows about from DOM elements before freeing them. That routine is called when you do something like overwrite element content with ".html()" or ".load()".

JQuery只能确保在通过库进行所有操作时没有泄漏。 jQuery中有一些名为“empty”和“cleanData”的例程,你可以仔细查看它们究竟发生了什么,但基本上代码只是在释放它们之前从DOM元素中分离出它所知道的一切。当您使用“.html()”或“.load()”覆盖元素内容时,会调用该例程。

Personally I'm pretty wary of terms like "guarantee" in a situation like this.

我个人对这种情况下的“保证”等术语非常谨慎。

#3


1  

rewritten to clarify further

改写以进一步澄清

There's actually 2 causes for memory leaks in the offered example. The first memory leak manifests due to creating a direct reference to a live DOM node inside a closure. The garbage collectors (JS & DOM) of legacy browsers such as IE6 cannot nullify such references. Hence the need to null out node references at the end of your function.

在提供的示例中,实际上有两个导致内存泄漏的原因。第一个内存泄漏由于创建对闭包内的实时DOM节点的直接引用而显现。诸如IE6的传统浏览器的垃圾收集器(JS和DOM)不能使这样的引用无效。因此需要在函数末尾清空节点引用。

jQuery circumvents this by default due to live DOM elements being attached to the jQuery object as attributes/properties, with which the afore mentioned garbage collectors have no trouble in determining null references. If the jQuery object has null references it's simply cleaned up and it's attributes/properties (in this case references to live DOM elements) along with it.

由于实时DOM元素作为属性/属性附加到jQuery对象,jQuery默认绕过这个,前面提到的垃圾收集器在确定空引用时没有任何问题。如果jQuery对象具有空引用,则只需清除它,并将其属性/属性(在本例中为对实时DOM元素的引用)与其一起清除。

So in order to avoid this memory leak, is to have an object maintain the reference to the live DOM node and then reference to the object in your closures. The closures will only maintain the references to the object and not the live DOM element since that reference belongs to the object.

所以为了避免这种内存泄漏,就是让一个对象维护对live DOM节点的引用,然后引用你的闭包中的对象。闭包只会维护对象的引用而不是live DOM元素,因为该引用属于对象。

// will still leak, but not due to closure references, thats solved.
function noLeak(){
    var obj= {
        elem: document.createElement('div')
    }
    obj.elem.onclick = function(){
        obj.elem.innerHTML = obj.elem.innerHTML + ".";
    }
}

This cleared the most obvious circular reference, but there's still a leak (onclick). The node has a reference to a function which has a reference to the object which in turn has a reference to the node. The only way to circumvent this leak, is to learn from the addEvent contest (lots of people worked to solve this leak ;) ). Coincidentaly, needed code can be found therein so my appologies for not supplying code for that ;)

这清除了最明显的循环引用,但仍然存在泄漏(onclick)。该节点具有对函数的引用,该函数具有对对象的引用,该对象又具有对该节点的引用。避免这种泄漏的唯一方法是从addEvent竞赛中学习(许多人努力解决这个漏洞;))。巧合的是,所需的代码可以在其中找到,所以我的appologies没有提供代码;)

Creating a wrapper for the event system adds some more code, but is essential. The main idea is to add a common eventHandler that delegates the event to an event cache/system which stores the required references. On an unload event, the cache gets cleared breaking circular references allowing the garbage collectors (JS and DOM) to tidy up their own corners.

为事件系统创建包装器会添加更多代码,但这是必不可少的。主要思想是添加一个公共eventHandler,它将事件委托给存储所需引用的事件缓存/系统。在卸载事件中,缓存被清除,打破循环引用,允许垃圾收集器(JS和DOM)整理自己的角落。