Friday, December 14, 2007

Anonymous Function Fun

Yahoo!'s Julien Lecomte wrote an interesting piece regarding the perils of using innerHTML.

One of the "bad things" that can happen are memory leaks caused by circular references. He provides this inner function which breaks the reference --
     (function (o) {

var a = o.attributes, i, l, n, c;
if (a) {
l
= a.length;
for (i = 0; i < l; i += 1) {
n
= a[i].name;
if (typeof o[n] === 'function') {
o
[n] = null;
}
}
}

a
= o.childNodes;

if (a) {
l
= a.length;
for (i = 0; i < l; i += 1) {
c
= o.childNodes[i];

// Purge child nodes.
arguments
.callee(c);

// Removes all listeners attached to the element via YUI's addListener.
YAHOO
.util.Event.purgeElement(c);
}
}

})(el
);

If you're new to JavaScript and if the syntax isn't familiar, it's
worth a closer look.

First, the function has no name. It's an anonymous function
that accepts one argument, the object 'o'.

Because it's an anonymous function and since there's no
reference to the function whatsoever, you can't use "this" to
refer to the function, but you can always do this --

arguments.callee(c);

All functions implicitly have the property "arguments" which
behaves somewhat like an array but isn't ( you can iterate
arguments like an array and get its length, but that's where the
similarities end). More about the arguments property is found
here.

Arguments themselves have properties and one of those is
"callee."

Think of the callee as the currently executing function which
substitutes for the "this" object.

Notice that once the function is defined, it's immediately
executed. We see this at the very end --

(function (o) {
...
})(el);

If all of this is still puzzling, we can rewrite Julien's function to
look like --

var breakCircularReference = function (o) {

var a = o.attributes, i, l, n, c;
if (a) {
l
= a.length;
for (i = 0; i < l; i += 1) {
n
= a[i].name;
if (typeof o[n] === 'function') {
o
[n] = null;
}
}
}

a
= o.childNodes;

if (a) {
l
= a.length;
for (i = 0; i < l; i += 1) {
c
= o.childNodes[i];

// Purge child nodes.
this
(c);

// Removes all listeners attached to the element via YUI's addListener.
YAHOO
.util.Event.purgeElement(c);
}
}
}

breakCircularReference(el);


We've given the once anonymous function a name -
breakCircularReference.
This allows us to reference it which means that "this"
refers to the function breakCircularReference. We no longer
have to use arguments.callee.

The name also allows us to call it --

breakCircularReference(el);

which I think is easier to read and understand for new
JavaScripters.

Have fun and happy holidays!

No comments: