Thursday, August 27, 2009

Javascript Shenanigans: Why I hate Prototype

Surprise, I'm not dead yet!

So anyway, I've been doing a decent amount of Javascript lately. This includes pure JS, and a few frameworks...jQuery, Prototype, and Dojo. I'm going to ignore Dojo for now since it really is a whole other beast than either jQuery or Prototype.

jQuery and Prototype behave in similar ways, providing a helper function like $() and allowing you a relatively standard API to do things that certain browsers do differently (like CSS). But jQuery is nice in that it stays out of your way, Prototype intrudes. It's the asshole that starts staring over your shoulder and saying everything you're doing is wrong as soon as it walks into the room.
Technically, Prototype grabs your keyboard and starts keymashing.
Odds are pretty good that a pure javascript application will stop working as soon as prototype is included in the page.
Take a look at the following example:

window.onload = function () {
var arr = [0,2,5,7,3];
alert(arr.length);
for (var i in arr)
alert(i);
};
The variable arr is an array with 5 elements. The first alert returns the expected "5", so you would expect that the "For .. in" loop (or a foreach loop) would alert you 5 values. In pure JS it does as expected, but when prototype gets in the game the for loop runs 43 time. Holy hot damn, what just happened to my code? Well, now you can't use foreach anymore because prototype all up in da hizzy.
The root of this problem is in the way that prototype works: by extending the DOM and javascript itself, through the "prototype" method.
In my copious free time I decided to see if I couldn't figure out how each of these frameworks implemented their "each" iterator function.

So, this emulates prototype's behavior:

Array.prototype.each = function (f,context) {
for (var i = 0; i<this.length; i++)
f.call(context,this[i]);
}

Yes, prototype is somewhat more elegant than this, but the basic premise is the same.
While jQuery's behavior is a bit more complicated than this (And I'm pretty certain that this is because John Resig is an immortal space traveler, come from the stars to give us amazing client side scripting languages), I feel it offers a better way of achieving the same effect.

(function () {
$ = function(elem) {
return new $.prototype.init(elem);
};
$.prototype = {
init: function(elem) {
this[0] = elem;
return this;
},
each: function(f) {
for (var i in this[0])
f.call(this[0][i]);
return this;
}
};
$.prototype.init.prototype = $.prototype;
})();

This allows you to iterate over the array in much the same way, except it doesn't modify the array so our original example would still loop 5 times and not 43.
But why does it work? Next time, on Build Environment.

--PXA

No comments: