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.

No comments: