One thing that you often hear people say about jQuery is that is has a neat plugin mechanism. It’s true there are a great many useful plugins available for jQuery and it makes them super-easy to write, but I want to let you in on a little secret:
jQuery.fn = jQuery.prototype;
That is jQuery’s plugin mechanism in its entirity. Now my point here is not that
jQuery somehow sucks (far from it) but that I suspect many folks could use
JavaScript better if they understood how flexible it is. In jQuery’s case, the
$()
function returns an instance of the jQuery
class, so if you add methods
to jQuery.prototype
then you can use those methods to change elements in new
and exciting ways. Behold:
$.fn.makeThemRed = function() {
this.css({color: 'red'});
return this;
};
$('p').makeThemRed();
$.fn
is just a shorter alias for jQuery.prototype
. The really important
thing here is that there is nothing special about the jQuery
class: all
objects (including prototypes) are extensible in JavaScript, and you can add and
remove properties and methods from any object whenever you like. Of course, the
effect of changes you make to a given object are entirely up to you, the point
is that you are allowed to make such changes.
One consequence of this is that you can modify the behaviour of existing methods
in any library you happen to be using. Take this snippet from Reiterate,
which adds new behaviour to a stack of Enumerable
methods in Prototype:
[Enumerable, Array.prototype, Hash.prototype,
ObjectRange.prototype, Ajax.Responders,
Element.ClassNames.prototype].each(function(object) {
$w('each all any collect detect findAll max \
min partition reject sortBy map find select \
filter every some').each(function(method) {
if (!object[method]) return;
var wrapped = object[method];
object[method] = function() {
var args = $A(arguments);
if (args[0]) args[0] = Function.from(args[0]);
return wrapped.apply(this, args);
};
});
});
This loops over all objects that include Enumerable
, and loops over the list
of modified methods for each object. For example, lets’s expand it for the
modification of Enumerable.collect
to make it a little clearer:
var wrapped = Enumerable.collect;
Enumerable.collect = function() {
var args = $A(arguments);
if (args[0]) args[0] = Function.from(args[0]);
return wrapped.apply(this, args);
};
So we store a reference to the original implementation of Enumerable.collect
,
then redefine it. Our new function can call the original function after
modifying the arguments a little – it effectively acts as an input filter. Some
parts of this implementation (each()
, $w()
, $A()
) are provided by
Prototype but the core idea – the method interception – is pure JavaScript.
Next time you’re using a fancy language feature provided by your favourite
library, take a look at how it’s implemented; you’ll probably find the
implementation is trivial and you might learn a little about JavaScript’s power.