Precise explanation of JavaScript <-> DOM circular reference issue - javascript

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?

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.

Related

DOM: why is this a memory leak?

Consider this quote from the Mozilla Docs on JavaScript memory leaks:
function addHandler() {
var el = document.getElementById('el');
el.onclick = function() {
this.style.backgroundColor = 'red';
}
}
The above code sets up the element to turn red when it is clicked. It
also creates a memory leak. Why? Because the reference to el is
inadvertently caught in the closure created for the anonymous inner
function. This creates a circular reference between a JavaScript
object (the function) and a native object (el).
Please explain the above reasons of leakage in a simple and concise way, I'm not getting the exact point.
Does the site/page face a security problem because of the leakage? How do I avoid them? What other code can cause memory leaks? How can I tell when a memory leak has occurred?
I'm an absolute beginner to the topic of memory leaks. Could someone clarify this stuff for me, step by step?Also can someone help me clarify this statement "This creates a circular reference between a JavaScript object (the function) and a native object (el)."
There are two concepts that will help you understand this example.
1) Closures
The definition of a closure is that Every inner function enjoys access to its parent's function variables and parameters.
When the addHandler() function finishes, the anonymous function still has access to the parent's variable el.
2) Functions = memory
Every time you define a function a new object is created.
What makes this example slightly confusing is that onclick is an event that can only be set to a DOM element once.
So surely el.onclick = function(){}; will just overwrite the old function right?
Wrong! every time addHandler runs, a new function object is created.
In conclusion:
Each time the function runs it will create a new object, with a closure containing el. Seeing as the anonymous function maintains access to el, the garbage collector cannot remove it from memory.
The anon function will maintain access to el, and el has access to the function, that is a circular reference, which causes a memory leak in IE.
Whenever you define a function in JavaScript an execution context is created for it; this execution context contains references to all the variables in the scope chain, starting from the global scope all the way up to the local scope:
function test()
{
var el = document.getElementById('el');
el.onclick = function() {
// execution context of this function: el, test
alert('hello world');
}
}
When test() is done, the anonymous function is not recycled yet because it's now assigned to an element of the DOM; i.e. it's being referenced by a property of the DOM element.
At the same time, the DOM element itself is also part of the function's execution context and now can't be recycled due to the cyclic reference, even though it's not immediately obvious that it's actually used; you can find a demonstration of that in this answer.
That said, nowadays, most JavaScript engines (even those found in IE) use a more advanced garbage collector that can identify unused variables a whole lot better, using techniques such as mark-and-sweep or generational / ephemeral garbage collection.
To make sure you don't run into problems on any browser (though, due to the typical lifespan of a page, this is mostly theoretical):
document.getElementById('el').onclick = function() {
alert('hello world');
}
Also see the more information section of the MS article on the issue:
This memory leak occurs because DOM objects are non-JScript objects.
DOM objects are not in the mark-and-sweep garbage collection scheme of
JScript. Therefore, the circular reference between the DOM objects and
the JScript handlers will not be broken until the browser completely
tears down the page.
but note that in contrast to what is stated in that article (memory will be reclaimed when the browser goes to a new page), this article confirms that a bug in IE 6 caused the memory to be leaked forever.
JavaScript's memory management usually works like this: "as long as it is possible to reach it, keep it". This is basically the paradigm that's behind any garbage collection driven memory model.
Garbage collectors tend to be very good at what they do, they even detect if a certain group of elements is only reachable within this very group of elements. Those groups are also called circular reference, since if you follow the references you'll end up at an element you already visited: you've run a circle.
However, in your example you actually have two objects from two different "worlds":
Internet Explorer uses its own garbage collection scheme for this, separate from the mechanism used by JavaScript. It is the interaction between the two that can cause memory leaks.
And this is exactly what happens and can cause memory leaks.

Where is my YUI 3 instance living? (plus some YUI event observation)

This question is mostly just out of academic interest.
I started using YUI 3 today and came across its sandbox concept. After thoroughly trying to find some objects (like my registered event handlers) inside the DOM I had to give up. I just couldn't find any instance objects ending up there.
I think I have closures mostly covered: even if the sandbox heavily relied on them, there would have to be at least one visible reference anywhere at least to parts like event handlers (else nobody could access anything and even closures would be garbage collected at some time in that case), right?
Where can I find the YUI instance inside the DOM (w/out assigning it to a global variable)?
Can I find it: are sandboxed YUI objects or parts of them living inside the DOM at all?
Getting more specific (and slightly off-topic) with an example:
I have a YUI Node object with attached YUI widgets and events. These Node objects are wrapping DOM nodes. Inspecting the DOM the wrapped nodes don't have any observers.
How are the events attached to the Node object triggered if nothing is wired up inside the DOM?
The only visible references with YUI 3 are the YUI global object and occasional element id that is assigned by YUI. Event subscriptions are made with native addEventListener or attachEvent, which don't leave evidence in the markup or innerHTML. There are some developer tools that are capable of showing subscriptions attached in this way, though, and they would show the YUI subscriptions.
You cannot find the YUI instance inside the DOM. That's the point of sandboxing. Instances of YUI exist in the JavaScript layer (technically, ECMAScript layer), not the DOM layer, exclusively inside closures unless you expressly assign them to a globally accessible variable or property or a globally accessible variable.

Should you dispose of jQuery objects?

A colleague of mine always sets their jQuery variables to null, to effectively dispose them, once they're finished with, e.g:
var bigThing = $(body);
// ...
// Do some stuff
// ...
bigThing = null;
Is that really necessary?
If you encapsulate your code into functions thats unnecessary as after the function finishes the local variables will be killed anyway when no reference of them is used elsewhere.
Holding onto a selector/variable (caching) might have some positive effect tho if you need to select the same thing over and over again versus selecting it only once and keeping the variable.
Short answer: no that's hardly ever necessary if you're using jQuery.
It depends on what you did with it. If you didn't attach any event handlers to the DOM Node, the garbage collector will clear it when it's no longer referenced.
But even if you did attach event handlers, jQuery will take care of them in functions like .remove() and .empty() by detaching all event handlers for you. So as long as you use jQuery to interact with the DOM, you're safe.
Without jQuery, if you've attached an event handler to the Node, the GC won't clear it, even after you've removed the Node from the DOM tree and you no longer have any references to it. This is because the DOM Node contains a reference to a JavaScript object (i.e. the event handler) and vice versa. This creates a cyclic reference across two separate systems; something most garbage collectors have trouble with.
For further reading I point you to Douglas Crockford's article on Memory Leaks.
It's not necessary but good habit to remove reference and release the memory
Though not entirely necessary, this can be done to ensure that the GC clears it in its next run (which it'll anyway do for all allocations to which you don't have even 1 reference).
In your example though, the $(body) object (the jquery extended object, not the DOM body object) will be cleared if you set bigThing to anything else (not necessarily null)
Javascript has its own garbage collector. So, it appears that you don't need to explicitly dispose of the objects.
But due to various reasons, like bad implementation of garbage collector etc., it may happen that their are some memory leaks.
By nullifying them explicitly, you specify to the browser that this memory has to be cleared in next garbage collection.
In conclusion, though it is not necessary to do this, it will be a good practice to nullify the JQuery/javascript objects.

How do you clear memory in Javascript?

var Obj = function(){}; var X = new Obj();
will X = null properly clear memory?
Also would this be equivalent?
var Obj = function(){};
var X = {};
X.obj = new Obj();
delete(X.obj);
EDIT
It would seem that although deleting X.obj would NOT immediately clear memory, it would help the garbage collection. If I don't delete X.obj, there would still be a pointer to an object and so the GC may not clean it up.
Although I'm picking #delnan's answer, if you're reading this you should def also catch Benubird's article.
I also notice I accidentally wrote delete(X) originally instead of delete(X.obj) - sorry.
The short answer is that you don't. delete simply removes a reference (and not in the way you try to use it, see the above link - delete is one of those language features few people actually understand), nothing more. The implementation clears memory for you, but it's not your business when (and even if, strictly speaking - this is why one shouldn't rely on finalizers in GC'd languages that offer them) it does. Note though:
Only objects that can be proven to be unreachable (i.e. no way to access it) to all code can be removed. What keeps references to whom is usually fairly obvious, as least conceptually. You just need to watch out when dealing with lots of closures, as they may capture more variables than you think. Also note that circular references are cleaned up properly.
There's a bug in old (but sadly still used) IE versions involving garbage collection of JS event handlers and DOM elements. Google (perhaps even SO) should have better material on my memory.
On the plus side, that means you won't get dangling pointer bugs or (save of course the aforementioned pitfalls) memory leaks.
No, that will not clear memory.
Read this:
http://perfectionkills.com/understanding-delete/
No - Javascript runs GC when it feels like it.
The Delete method only deletes the reference - not the object. Any other references would be left out in the open waiting for the garbage collector.
JavaScript has its own GC, and it will run around and clean things up when nothing refers to them anymore.
I still think it's a good practice to null objects.
Deleteing an object also helps the GC because it will see something dangling, and say "I'm going to eat you because you're all alone (and now some cynical laugh)".
You should look at Deleting Objects in JavaScript
Even though there's a GC, you still want to ensure your script is optimized for performance as peoples computers, browsers, and fricken toolbars (and the number of them), will vary.
Generally speaking, memory management in Javascript is user-agent-specific. The basics of the garbage collector are through reference-counting. So, by setting a reference to null (using the delete keyword or by explicit assignment), you can assure yourself that a reference will be cleaned up, IF the object does not have any references that will live outside of its creation scope. That being the case, the GC will have already cleaned up any objects or variables whose scope has ended without your explicitly setting it to null.
There are some things to take care of, though - circular references are easy to create in JS, especially between a DOM element and an object. Care must be taken to clear (or not create in the first place) references to and/or from DOM elements within objects. If you do create a to/from reference related to DOM, be sure to explicitly clean them up by setting the references to null - both on your object and on the DOM element. Simply setting a parent object to null is not sufficient if there are child objects with references to/from DOM or localStorage because those references will live on, and if there was any reference from the child to the parent, then the parent will live on in memory because of that reference.
Web pages can actually leak trash in your memory this way - after you navigate away, the circular references keep objects and DOM elements in memory until you've restarted the browser!
An article on the subject: http://docstore.mik.ua/orelly/webprog/jscript/ch11_03.htm, and another detailed look: http://blogs.msdn.com/b/ericlippert/archive/2003/09/17/53038.aspx
JavaScript memory is generally handled similarly to Java - I mean there is (or there should be) a garbage collector which would delete the object if there is no references to it. So yes, simply "nullifying " the reference is the only way you should "handle" freeing memory, and the real freeing is the JS host part.

Augment a DOM element

Is it possible to augment a DOM element by adding a new property to it as augmenting a normal JavaScript object? For example, can we add a new property of a button or an input, say a value that indicates when was the last time user click on it?
Yes. You can sometimes find it described as bad form. The obvious drawback is that in later versions of Web standards, the DOM element may have new properties added to it which will clash with your custom property, so if you do use this technique, choose a name that is unlikely to clash, e.g. incorporate your company name into it.
Another potential problem is that DOM objects in some browsers are not properly garbage collected. Instead they are reference counted. This means that if you create a system of circular references through custom properties, you may cause a memory leak.
For example, this may cause a memory leak, because there is a circular reference between the DOM and javascript objects:
var myJsObj = {};
var myDomElt = document.getElementById('myId');
myJsObj.domElt = myDomElt;
myDomElt.jsObj = myJsObj;
It is, however, safe to add a property that just holds a primitive value:
myDomElt.fullTitle = "My DOM Element";
Although the warning that the DOM standard may change is still important; what if HTML7 defines a fullTitle property on DOM elements? Your site will have unpredictable behaviour.
An alternative is to use the $.data feature of jQuery:
$(myDomElt).data('fullTitle', 'My Dom Element');
var retrieved = $(myDomElt).data('fullTitle');
It's possible but is a really bad idea. Daniel Earwicker's answer mentions some reasons why it's a bad idea. I'd add these:
DOM nodes are host objects and host objects can do what they like. Specifically, there is no requirement in the ECMAScript spec for host objects to allow this kind of extension, so browsers are not obliged to allow it. In particular, new browsers may choose not to, and existing code relying on it will break.
Not all host objects in existing browsers allow it. For example, text nodes and all ActiveX objects (such as XMLHttpRequest in older IE and XMLDOM objects, used for parsing XML) in IE do not, and failure behaviour varies from throwing errors to silent failure.
In IE, the ability to add properties can be switched off for a whole document, including all nodes within the document, with the line document.expando = false;. Thus if any of the code in your page includes this line, all code relying on adding properties to DOM nodes will fail.

Categories