Friday, August 28, 2009

Javascript Shenanigans: How jQuery works. Kinda.

Every time I re-read the source code to jQuery I understand something new about how it works. It's actually rather enlightening, figuring out how it works.

I'd like to explain the way the "jQuery" code from the previous post works, since it uses a decent amount of more advances javascript and led me to a greater understanding of the language.

So, this:

(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;
})();

Broken down:
First, to make sure that everything is setup as soon as the file is loaded (before any of the document is even parsed by the browser), the setup is wrapped inside an anonymous block. I'm not sure what the proper javascript term for this is, but it's very similar to the "static { }" block from Java, where any code contained within it is immediately executed as soon as the class is loaded by the JVM. As an example:
(function (val) { alert(val); })("HI MOM");
Would immediately alert the string "HI MOM" before any content loads.

After this, we define the $ function. The function does the simple job of returning a new instance of "init". While creating instances of functions may look odd, Javascript allows this. Functions are often used to declare what's commonly thought of as an object. While "function $(elem)" is legal, I find the "var $ = function(elem)" notation makes a little more sense.

Javascript does not formally define its classes, so multiple instances of "object" can each have completely different properties and methods. Properties and methods are assigned at runtime. The .prototype method, mentioned in the previous post gives us a way to make sure all instances of an object have specific members. So, $.prototype is an object containing additional members of the $ object, which will be applied to every $ around.

In it, we define an init method which stores us a reference to the element we gave it, and then returns an instance of "init". After that we define an each method, which simple iterates whatever "this[0]" is and returns. While it's not strictly necessary to return from this function it let's us do something really cool: chaining. Look it up at jQuery's site, it rocks. This each method accepts a single parameter, a callback function, which is executed on each iteration of the loop. Rather than simply calling the function like "f()", we can use the more flexible .call() method. This basically allows us to "redefine the execution context for the given function", or in simple terms it lets us change the value of the "this" variable.

This final line adds $'s prototype to init as a prototype. This is why jQuery plugins are written
jQuery.fn.nameofmyplugin = ...
In real jQuery, jQuery.fn = jQuery.prototype, so your plugin adds another object to $'s prototype, which also adds it to $.init's prototype.

g'night. tired. bye.

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