This time round, I took on jQuery: what can we learn about writing JavaScript code from reading the latest version of jQuery? This was not a webinar about what jQuery does but how it does it. I used version 1.5.2 of jQuery. (You can watch the webinar – full of my usual ums and ers, sorry! – here.)
1. Again, like last time with underscore.js, the code is structured like this:
(function (window, undefined) {
// lots of code
})(window);
That is, as an autoexecuting anonymous function. Again: a very common pattern in JavaScript. This time notice that the anonymous function is called with one parameter, but that it is declared with two. The undeclared
parameter will get set to the undefined
value. This is merely a trick to make sure we have a valid value for undefined
: JavaScript stupidly allows the global variable undefined
to be changed.
I’m in two minds about the messing around with window
. I’d probably write it like this:
(function (undefined) {
var window = this;
// lots of code
})();
The this
parameter will automatically get set to the global object when the anonymous function is called.
2. The main point about this is that local variables (and remember scope is function-based in JavaScript) are resolved faster than any other variable. So declaring window
as a local variable means we can access it faster than just letting it be there at the end of the resolution chain.
3. Next thing to note is the next to last line of code:
window.jQuery = window.$ = jQuery;
We’re setting the global variables jQuery
and $
to the local variable. Prior to this point, all the jQuery code used the local jQuery
variable.
4. Back to the top and we have this:
var document = window.document;
Again, we’re capturing some value as a local variable for performance reasons.
5. Now we get some right weird code:
var jQuery = (function() {
// Define a local copy of jQuery
var jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context, rootjQuery );
},
// other code
// Expose jQuery to the global object
return jQuery;
})();
So… the local jQuery
is the return value from an autoexecuting anonymous function that declares a local jQuery
variable (actually a function) and returns it. Got that?
As it happens this rather weird way of doing things is because of the way the source code is written and maintained. We’ll get back to that in a moment. (By the way, that final comment is a bit misleading; it’s not until much later on that we expose jQuery
globally).
6. Take a note of these lines:
// Map over jQuery in case of overwrite
_jQuery = window.jQuery,
// Map over the $ in case of overwrite
_$ = window.$,
We’re capturing the current values of the global jQuery
and $
variables that we’re going to overwrite later (see point 3 above). The reason for this is so we can declare a noConflict
method:
noConflict: function( deep ) {
window.$ = _$;
if ( deep ) {
window.jQuery = _jQuery;
}
return jQuery;
},
This method will reset the original values of the two global variables we’ve stomped all over in point 3. This does mean that we can, if we want, use two libraries that use $
as an identifier (Mootools is an example of this): just call $.noConflict(false)
and use jQuery
thereafter.
7. Notice again that we capture the values of some prototype methods for speed purposes:
// Save a reference to some core methods
toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty,
push = Array.prototype.push,
slice = Array.prototype.slice,
trim = String.prototype.trim,
indexOf = Array.prototype.indexOf,
Same principle: local variables implies faster access. Plus it’s easier to type…
8. The next thing that struck me when I was reading the code was the declaration and use of the extend
method. This is like a really popular method in the jQuery codebase and it’s worth investigating why.
An extend function is a pretty cool function to have in your toolbox anyway. What it does is to take an object (call it the target) and add all the properties that are present in another (source) object. Or source objects. The canonical example of its use is that of an options object (sometimes called a hash) containing properties for the individual options. In your library code you can have a default options object containing default values. Here’s an example of its use, merging the default options with a user-supplied set of options:
var defaultOptions = { animate: true, color: "red" };
var myOptions = { color: "blue" };
var actualOptions = $.extend({}, defaultOptions, myOptions);
// result: { animate=true, color="blue"}
So we have as target an empty object. We apply the properties from the default hash, and then we apply my changes. Simple enough, and that’s one way of using jQuery’s extend
.
The first nifty thing about jQuery’s version is that it can accept an optional boolean value as the first parameter. If this value is true, it means that properties in the target are not overwritten if they exist already. Instead it’s the properties of the object properties that will be overwritten. Simples way is to look at an example:
var target = { point: { x: 0, y: 0, color: "red"} };
var source = { point: { x: 10, y: 20 }, animate: true };
$.extend(true, target, source);
The result is target will be set to { point: { x:10, y:20, color: “red”}, animate: true }
. If you like the source is “deep merged” into the target.
Anyway the main point about this is to go read the code to see how the jQuery developers pulled off having an optional initial boolean parameter (it involves playing around with the arguments array). You may find the technique useful in your own code.
9. The other interesting behavior of jQuery’s extend
is this: if there is only one parameter, an object, it’s assumed to be a source object and the target object is assumed to be jQuery itself. If you look at the source code, you will see lots of examples of extend
being called like this:
jQuery.extend({
//code defining a set of properties/methods
});
Why do it this way instead of having a monolithic bit of code defining the jQuery
object?
It turns out that jQuery is implemented by a team of developers each working on different parts of the main source code. The source code comprises a set of a dozen or so individual .js files and the makefile concatenates them all into the single jQuery.js file that we’ve been using. Here’s the relevant code from the makefile:
BASE_FILES = ${SRC_DIR}/core.js\
${SRC_DIR}/deferred.js\
${SRC_DIR}/support.js\
${SRC_DIR}/data.js\
${SRC_DIR}/queue.js\
${SRC_DIR}/attributes.js\
${SRC_DIR}/event.js\
${SRC_DIR}/selector.js\
${SRC_DIR}/traversing.js\
${SRC_DIR}/manipulation.js\
${SRC_DIR}/css.js\
${SRC_DIR}/ajax.js\
${SRC_DIR}/ajax/jsonp.js\
${SRC_DIR}/ajax/script.js\
${SRC_DIR}/ajax/xhr.js\
${SRC_DIR}/effects.js\
${SRC_DIR}/offset.js\
${SRC_DIR}/dimensions.js
MODULES = ${SRC_DIR}/intro.js\
${BASE_FILES}\
${SRC_DIR}/outro.js
For fun, here’s the outro.js file:
window.jQuery = window.$ = jQuery;
})(window);
So, for ease of developing & debugging the devs can use the individual .js files (in the right order of course). Since they are separate, they use the extend
function in order to merge their pieces of functionality into the main jQuery object. This technique might be an idea to pursue if you are writing a large JavaScript library.
10. A quick couple of extra points before I finish. The first is a simple way of creating a constant that is a string array. Typing it out by scratch (or extending an existing one) is a pain in the neck, making sure that you have typed the quotation marks and the commas. An easier idea is to use this pattern:
props: "altKey attrChange attrName bubbles … view wheelDelta which".split(" "),
In other words define a string with the keywords separated by spaces and then call split()
on it. Much easier to type.
11. Here’s the final second point for now. There are a few objects that are used as “translation hashes”; that is an object whose property keys are simple identifiers and whose values are more complicated or are magic numbers. One example is jQuery.props
, but the better one is jQuery.fx.speeds
. This hash contains properties whose keys are human-readable words (with semantic meaning) and whose values are values in milliseconds.
speeds: {
slow: 600,
fast: 200,
// Default speed
_default: 400
},
It’s far easier to remember “slow” and ”fast” than the actual magic values. You can of course add others if you find you need them:
$.fx.speeds[veryfast] = 150;
It’s certainly a technique to be aware of.
That’s it for now. In three weeks’ time I’ll be presenting a second webinar on reading and understanding jQuery. Hope to see you then.
Free DevExpress Products – Get Your Copy Today
The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the
DevExpress Support Center at your convenience. We’ll be happy to follow-up.