Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Precise explanation of JavaScript <-> DOM circular reference issue

One of the touted advantages of jQuery.data versus raw expando properties (arbitrary attributes you can assign to DOM nodes) is that jQuery.data is "safe from circular references and therefore free from memory leaks". An article from Google titled "Optimizing JavaScript code" goes into more detail:

The most common memory leaks for web applications involve circular references between the JavaScript script engine and the browsers' C++ objects' implementing the DOM (e.g. between the JavaScript script engine and Internet Explorer's COM infrastructure, or between the JavaScript engine and Firefox XPCOM infrastructure).

It lists two examples of circular reference patterns:

  • DOM element → event handler → closure scope → DOM

  • DOM element → via expando → intermediary object → DOM element

However, if a reference cycle between a DOM node and a JavaScript object produces a memory leak, doesn't this mean that any non-trivial event handler (e.g. onclick) will produce such a leak? I don't see how it's even possible for an event handler to avoid a reference cycle, because the way I see it:

  • The DOM element references the event handler.

  • The event handler references the DOM (either directly or indirectly). In any case, it's almost impossible to avoid referencing window in any interesting event handler, short of writing a setInterval loop that reads actions from a global queue.

Can someone provide a precise explanation of the JavaScript ↔ DOM circular reference problem? Things I'd like clarified:

  • What browsers are effected? A comment in the jQuery source specifically mentions IE6-7, but the Google article suggests Firefox is also affected.

  • Are expando properties and event handlers somehow different concerning memory leaks? Or are both of these code snippets susceptible to the same kind of memory leak?

    // Create an expando that references to its own element.
    var elem = document.getElementById('foo');
    elem.myself = elem;
    
    // Create an event handler that references its own element.
    var elem = document.getElementById('foo');
    elem.onclick = function() {
        elem.style.display = 'none';
    };
    
  • If a page leaks memory due to a circular reference, does the leak persist until the entire browser application is closed, or is the memory freed when the window/tab is closed?

like image 539
Joey Adams Avatar asked Apr 10 '12 16:04

Joey Adams


1 Answers

It's probably not worth reproducing all the content in these links, so I'd suggest you do some reading and a look at the other Google search hits:

javascript, circular references and memory leaks

Do you know what may cause memory leaks in JavaScript?

http://www.ibm.com/developerworks/web/library/wa-memleak/

http://www.ibm.com/developerworks/web/library/wa-sieve/index.html?ca=drs-

http://code.google.com/p/google-web-toolkit/wiki/UnderstandingMemoryLeaks

The worst memory leaks are in IE6 where the leaks are permanent (even after you leave the affected web page). The other leaks are generally only while you're on that specific page and get cleaned up when you leave the page.

The fact is that the browser is supposed to be able to handle circular references. It's supposed to be able to see that even though a DOM element is still being referred to by a JavaScript element, that the JavaScript element itself only exists because the DOM element is still alive and thus there is no true outside reference left to the DOM element. It is this recognition that older versions of IE were bad at. Thus in your code references that involve event handlers, the garbage collector needs to be smart enough to know that the only references left to the DOM element in JavaScript are references that themselves would go away when the DOM element and it's event handlers were removed - thus there are no true outside references so it's safe to remove both the DOM element and the event handler. This is a more complicated version of the general circular reference problem that all garbage collectors have to handle where object A refers to object B and object B refers to object A, but no other object refers to either A or B, thus both can be freed.

jQuery's .data() makes things more reliable because the older versions of IE had a particular problem with properties that were added to a DOM element and did not handle circular references properly involving the data in those properties and thus would not free things when it should have (a leak). .data() works around that by only using one added property on the DOM element which is a safe, non-leaking string. That string is then a key into a JavaScript object that can contain all the properties that you would like to associate with the DOM element. Because these properties are all stored in plain JavaScript, where the browsers don't have the circular reference bugs, doing it this way doesn't cause leaks.

It's important to realize that there may still be some circular references and that's OK. The work-around is to move the circular references to a place that the browsers handle them appropriately rather than putting them in the place that the browsers have bugs.

like image 178
jfriend00 Avatar answered Sep 28 '22 07:09

jfriend00