My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Friday, April 24, 2009

div[expando] = null OR div.removeAttribute(expando)?

Today we had an interesting conversation in the jQuery developer mailing list, and "surprise", it was about Internet Explorer and memory leaks.
I instantly started some test to understand what the hell is going on there ... and I encountered a dilemma which is not simple at all!

div.prop = 1 IS NOT div.setAttribute("prop", 1)

Some developer thinks that the usage of setProperty and getProperty is basically the same of a manual assignment as generic DOM Element property ... well, they are wrong.
First of all, if we are working with XML we all know that it is not possible to assign a property in this way:

xmlNode.prop = 123; // Error!!!
xmlNode.setAttribute("prop", 123); // OK, but be careful!

The main difference between manual property assignment and the usage of setAttribute/getAttribute is that we are changing HTML or XML properties of the node without control.
The reason is simple, how manual properties are assigned/retrieved is browser dependent, and the only way to be sure we are not modifying a node only internally is to use the setProperty method.
Obviously, it is not that simple in any case still because of browsers implementations. Here a couple of examples:

var div = document.createElement("div");
div.__expando__ = 123;

// FireFox and other browsers
div.getAttribute("__expando__"); // null

// Internet Explorer
div.getAttribute("__expando__"); // 123

Accordingly, the only browser that manages direct assignment as setAttribute shortcut is Internet Explorer.

Moreover, cases are really differents between IE and other browsers ...

While everybody uses setAttribute as a method to change the node in its HTML or XML nature, Internet Explorer uses this method to assign whatever value. Example:

var div = document.createElement("div");

// set an object as an atribute
div.setAttribute("__expando__", {a:"b"});

// FireFox and others will obviously convert the second argument as a string
// to make node representation possible as HTML or XML

// Internet Explorer will set that object as an hidden attribute
// which means that that property will not exists in the
// HTML or XML representation of that object, but will be a property

// this call ...
div.getAttribute("__expando__");

// will generate [object Object] in FireFox and others
// not because we are dealing with an object
// but because the object has been converted into a string
// during setAttribute assignment

// In internet Explorer that cal will retrieve the original
// Object {a:"b"} and this call will produce: "b"
div.getAttribute("__expando__").a; // b in IE, undefined in other browsers


Another interesting thing is that we cannot use delete div.__expando__ in internet explorer for the reason I said before: we cannot delete an attribute via delete, we need to use the removeAttribute method.


var div = document.createElement("div");
div.__expando__ = 123;

delete div.__expando__; // Error in IE

But since IE set attributes of any type, we should bear in mind that everything that is not instanceof Object will be present in the HTML representation of that Element.

var div = document.createElement("div");
div.n = null;
div.obj = {a:"b"};

alert(div.outerHTML);
// <DIV n="null"></DIV>

Accordingly, whatever reason we have behind leaks, we should bear in mind that if that node has been stored somewhere, it will bring with itself every property = notAnObject; included numbers, null, whatever.

To create a memory leak we can simply do something like this:

var div = document.createEement("div");
div.__expando__ = div;

That's it, even if we remove that div and we delete its related variable (div), IE will not remove that node (if present in the DOM) from its used RAM.
Since some developer know it, they obviously try to avoid leaks like this using a syntax which makes sense but which leave traces of "their operate":

var div = document.createEement("div");
div.__expando__ = {iama:"complex object", orwhatever:"value"};

// when div is removed/destroyed
div.__expando__ = null;

// well, as we have seen before:
div.outerHTML;
// <DIV __expando__="null"></DIV>


To completely remove properties then, all we need to do is to use removeAttribute:

var div = document.createEement("div");
div.__expando__ = {iama:"complex object", orwhatever:"value"};

// when div is removed/destroyed
div.removeAttribute("__expando__");

// well, as we have seen before:
div.outerHTML;
// <DIV></DIV>


If you test with a memory leaks detector, you will realize that the result is exactly the same while the final node, leaked or not, will be as clear as possible from other libraries and conflicts will be reduced as well (getAttribute("__expando__") is setted as null will return the string "null" rather than the null value usually returned by getAttribte when it is not there).

What's next? Understand how much performances will be affected and when it is worthy to use removeAttribute rather than = null but at least now we have proven that setAttribute, getAttribute, and direct property assignment are not the same thing.

1 comment:

a.in.the.k (@ainthek) said...

Just one more observation:

HTML comes to client with server side value "input value='orig'.

Dump i.value and i.getAttribute("value") in window.onload handler.

MSIE 7.0: orig, orig
FF: 3.5.6: orig, orig

Change value by typing to: "new".
Click refresh (or navigate away and back). The "new" should be written in the field (if browser remembers user typed values)
Now look at the dump:

MSIE 7.0: new, new
FF: new, orig

In msie the original HTML markup value is not accessible (lost ?) by getAttribute after return to the page.