开发者

JQuery Garbage Collection - Will This Be Clean?

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.

(IE 6 can't do 开发者_运维问答it at all and IE7 can only do it between page requests):

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:

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

JQuery Version (Does NOT Leak):

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:

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

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


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

// 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).

Oh, and the line:

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

ensures that it just runs on IE.


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()".

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


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.

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.

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.

// 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 ;)

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜