In my previous post I extended
Function.prototype. Since functions are fundamental components of JavaScript, I thought it worth showing a few more extensions that have proven useful.
Function.prototype.bind = function (obj)
{
var method = this;
var f = function () { return method.apply(obj, arguments); }
return jscore.addMeta(f, { binding : "bind", target : method });
};
Function.prototype.bind2 = function (obj)
{
var method = this;
var f = function () {
var args = jscore.arrayCat([this], arguments);
return method.apply(obj, args);
};
return jscore.addMeta(f, { binding : "bind2", target : method });
};
Function.prototype.head = function ()
{
var method = this, first = jscore.arrayCat(arguments);
var f = function () {
var args = jscore.arrayCat(first, arguments);
return method.apply(this, args);
};
return jscore.addMeta(f, { binding : "head", target : method });
};
Function.prototype.tail = function ()
{
var method = this, last = jscore.arrayCat(arguments);
var f = function () {
var args = jscore.arrayCat(arguments, last);
return method.apply(this, args);
};
return jscore.addMeta(f, { binding : "tail", target : method });
};
Function.prototype.returns = function (ret)
{
var method = this;
var f = function () { method.apply(this, arguments); return ret; };
return jscore.addMeta(f, { binding : "returns", target : method });
};
Function.prototype.seal = function ()
{
var method = this;
var f = function () { return method(); };
return jscore.addMeta(f, { binding : "seal", target : method });
};
And the helper functions:
$namespace("jscore", {
addMeta : function (obj, meta)
{
if (!obj.$meta)
obj.$meta = {};
for (var n in meta)
obj.$meta[n] = meta[n];
return obj;
},
arrayCat : function ()
{
var ret = new Array(jscore.arrayLen.apply(null, arguments));
for (var i = 0, k = 0; i < arguments.length; ++i)
for (var a = arguments[i], n = (a ? a.length : 0), j = 0; j < n; ++j)
ret[k++] = a[j];
return ret;
},
arrayLen : function ()
{
var ret = 0;
for (var a, i = 0; i < arguments.length; ++i)
if (a = arguments[i])
ret += a.length;
return ret;
}
}); // jscore
While the basic goal of these methods is to help create
closures more cleanly and efficiently, one of them has proven to be by far the most useful:
bind.
If you haven't used closures before, you are really missing out on the power of JavaScript. But, if you have used closures, you've probably experienced their dark side: memory leaks, especially in
IE.
This is a well known problem for IE, but these methods can help that situation. Even outside of IE, closures can hold on to objects that aren't needed, thus producing a resource leak or at least a less-than-desirable cost. The problem with closures stems from the fact that the closure implicitly keeps references to all variables in its lexical scope, whether it needs them or not. In other words:
function foo (x, y)
{
var a = [ ... ]; // lots of stuff
document.getElementById("bar").onclick = function () { ... }
}
In the above code, the
onclick handler function captures a, x and y and holds on to them for its entire life. If the function doesn't need all of these objects, their persistence is wasteful. In this situation,
head (or
tail) can be used to reduce the bound variables to only what is needed. For example,
function clickHandler (x, y)
{
...
}
function foo (x, y)
{
var a = [ ... ]; // lots of stuff
document.getElementById("bar").onclick = clickHandler.head(x, y);
}
Now, the garbage collector can cleanup
a, but
clickHandler can still reference
x and
y. More importantly, this helps in IE in the following example:
function foo (x, y)
{
var a = [ ... ]; // lots of stuff
var el = document.getElementById("bar");
...
el.onclick = function () { ... }
}
The above code illustrates a classic memory leak situation in IE. The problem has already been well
explained, but here's how
head helps:
function clickHandler (x, y)
{
...
}
function foo (x, y)
{
var a = [ ... ]; // lots of stuff
var el = document.getElementById("bar");
...
el.onclick = clickHandler.head(x, y);
}
With this approach, neither
clickHandler nor the closure function created by
head keep a reference on the
el variable. This removes the reference cycle and avoids the memory leak.
That is all nice, but
bind has an even more useful feature. In fact it is this feature that inspired me to write these methods in the first place. It was only after some use that I decided to add the ability to store parameter values, because (to me) the most important was
bind.
In my past work, programming in C++ was always complicated by the need to preserve the blasted
this pointer. This is especially problematic when working with C API's that make no such accomodations for tracking state. Fast forward a bit. When I started programming in JavaScript, I noticed it felt a lot like C. I had functions, and I had data, but often enough I had no good way to write methods that were associated with an object.
The browser interface is full of places where
this pointers are easily lost. Take
setTimeout for example. It can call a function after a specified amount of time. Well and good, but where's my bloody
this pointer?!? If
setTimeout and its partner
setInterval were alone, I may not have sought a general solution. Sadly, they are far from alone. Even in the above examples, the
onclick event handler does not preserve any
this pointer. Even worse, DOM elements call these handlers with themselves as the
this pointer! Talk about frustrating, but I'll get back to events another time.
Once or twice I've had the need to connect a method that processed all provided arguments (like
arrayCat and
arrayLen) to a caller that would supply an extra parameter or two. You can imagine the confusion that caused! At first, I threw in a closure and then realized the error of my ways. OK, it was a memory leak that caused me to realize my mistake. So, there's a pair of methods that are unlikely to ever get more complex:
returns and
seal.
Together
returns and
seal fixed my memory leak. The
seal method cuts off any supplied arguments that it receives and calls the bound function. The
returns method took care of returning the proper value.
Of course, these are really cool in combination:
function foo ()
{
for (var i = 0; i < arguments.length; ++i)
...
};
function bar (x, y)
{
var el = document.getElementById("foobar");
var fn = foo.head(x, y).seal().returns(false);
el.onclick = fn;
};
Though highly dubious, this example illustrates chaining
head,
seal and
returns. Together, these methods can go a long way to allowing arbitrary methods to be connected without playing with the fire of naked closures.
Lastly, these binding methods required a slight tweak to the
getName method. This ensures that it will report its correct name as well as the real target method to which it is bound.
Function.prototype.getName = function ()
{
if (!this.$meta)
{
var name = this.name || this.toString().match(/function (\w*)/)[1];
name = name || "~anonymous~";
this.$meta = { fullname : name, name : name };
}
var s = this.$meta.fullname;
if (this.$meta.target)
s += "~"+this.$meta.binding+">" + this.$meta.target.getName();
return s;
}
Enjoy!