Webinar wrap-up: Julian on JavaScript–reading jQuery (part 2)

ctodx
23 May 2011

I decided to take a slightly different tack with the second part of my “reading jQuery” webinar duet. First, I looked at the whole of the jQuery project with regard to what we might learn for our own multi-developer JavaScript project and, second, I discussed what the heck is returned from a call to jQuery or $ and what we might learn from that.

You can watch the webinar here.

Writing a JavaScript codebase in a team

Unbox/Unwrap. Pralus Papouasiephoto © 2009 Everjean | more info (via: Wylio)You can find all of the code for the jQuery project in a Git repository. Fro the root, open up Makefile. This contains the commands needed to build JQuery using either make or ant. Note the productions BASE_FILES and MODULES: they define the individual code files that, once concatenated, produce jquery.js (and its minified version). The files are concatenated a little further on: search for the call to the system command @@cat.

The first and last files mentioned in MODULES are intro.js and outro.js. If you look at those files in the src folder you’ll see that they are the preamble and postscript to jquery.js:

/*!
* jQuery JavaScript Library v1.6.1
* http://jquery.com/
*
* Copyright 2011, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* Includes Sizzle.js
* http://sizzlejs.com/
* Copyright 2011, The Dojo Foundation
* Released under the MIT, BSD, and GPL Licenses.
*
* Date: Thu May 12 15:04:36 2011 -0400
*/
(function (window, undefined) {

  // Use the correct document accordingly with window argument (sandbox)
  var document = window.document,
	navigator = window.navigator,
	location = window.location;

  // rest of code

  window.jQuery = window.$ = jQuery;
})(window);

code.js is a special case (it’s the foundation of jquery itself), so take a look at deferred.js. Notice it has a preamble and postscript of its own, forming an anonymous auto-executing function:

(function (jQuery) {

// rest of deferred.js

})(jQuery);

But that code is not found in the final jquery.js: it is stripped out by the sed system command in the same production as the “”@@cat” command:

@@cat ${MODULES} | \
	sed 's/.function..jQuery...{//' | \
	sed 's/}...jQuery..;//' | \
	sed 's/@DATE/'"${DATE}"'/' | \
	${VER} > ${JQ};

That same code inserts the current date and version number into the code.

This wrapping of the individual JavaScript modules with an anonymous auto-executing function means that you can work on a single module without affecting any of the other modules. You can load the current jQuery source, then load your particular module, and everything in that module will replace what’s already in jQuery. Hence you can design, develop, test individual modules without affecting the rest of the team.

Notice also from the Makefile that a module called selector.js is concatenated, but that it doesn’t appear in the src tree. That’s because it’s created from sizzle.js, another open source library.

Another part of the Makefile shows that the build entails the use of jsLint (the source is under the build folder) to perform a code-checking pass over the code before it’s built. jsLint is a code checker from Douglas Crockford and is usually found in an interactive version at jslint.com. Here it is being used locally to check the jQuery code as it’s being built. The minifier is also called as part of the jQuery build process: it’s encapsulated by uglify.js (apt name!) and uses parse-js.js, and so on.

So, in conclusion, the entire build process for jQuery points the way to how to structure a multi-developer team working on the same codebase. All you need is a Linux machine or a Mac or Windows supplemented with Cygwin in order to build jQuery yourself.

The jQuery Collection

What the heck does a call of the form $(someSelector) return? The first question that should come to you is: is it an array? After all, it holds a set of elements and even comes with an each method. Let’s investigate. I wrote a function to “dump” such an object:

var dumpObj = function (obj) {
  console.log($.isArray(obj));
  console.log(obj.length);
  if (obj.length > 0) {
    console.log(obj[0]);
  }
};

var p = $(".post");
dumpObj(p);

p = $("#post_280");
dumpObj(p);

The function first writes out if the object is an array (using the jQuery helper function), then it writes out the value of the length property, and if that is non-zero, the first element. (The code is designed to run on my personal blog’s home page: the structure of the page is fairly simple and consists of a set of divs of class post, with each post having a unique id.)

If you run this you’ll see that the object returned by the jQuery function is not an array, but is array-like: it has a length property, and it has a set of properties named from 0 to length-1. But the similarities with an array end there; there are no array methods like slice, splice and so on. Let’s call the returned “wrapped set” (as the jQuery authors call it) a jQuery collection.

Let’s look at the code for the jQuery function. It merely creates a new object using the init constructor.

    var jQuery = function (selector, context) {
      // The jQuery object is actually just the init constructor 'enhanced'
      return new jQuery.fn.init(selector, context, rootjQuery);
    },

The cd:init constructor uses the jQuery prototype as its prototype.

    jQuery.fn = jQuery.prototype = {
      constructor: jQuery,
      init: function (selector, context, rootjQuery) {
        // code
      },

      // Start with an empty selector
      selector: "",

      // The current version of jQuery being used
      jquery: "1.6.1",

      // The default length of a jQuery object is 0
      length: 0,

      // The number of elements contained in the matched element set
      size: function () {
        return this.length;
      },

      toArray: function () {
        return slice.call(this, 0);
      },

      //etc

And init is defined as part of that prototype. Notice the length property there, and an empty selector as well as some other properties and methods.

OK, then, so what are the elements of a jQuery collection? Time for a bit more exploratory code:

var isDOMElement = function (obj) {
  return !!obj.nodeType;
};

var dumpObj = function (o) {
  if (o.length > 0) {
    var e = o[0];
    console.log(e);
    console.log("Is DOMElement? " + isDOMElement(e));
    if (isDOMElement(e)) {
      console.log(e.nodeName);
    }
  }
  else {
    console.log("Is DOMElement? " + isDOMElement(o));
    if (isDOMElement(o)) {
      console.log(o.nodeName);
    }
  }
};

var p = $(".post");
dumpObj(p);

p = $("#post_280");
dumpObj(p);

p = $(p);
dumpObj(p);

I’ve written a small helper function that determines if the object passed in is a DOM element. It uses the presence/absence of the cd:nodeType property to determine this: all DOM objects have this property defining the type of the element. (1 is an ELEMENT_NODE, 2 is an ATTRIBUTE_NODE, 3 a TEXT_NODE, and so on). So we’ll assume that if the object has such a property it is a DOM element of some description. I’ve also expanded the dumpObj function a bit more. Finally I call jQuery on a class selector, an id selector, and just for fun on that last jQuery collection.

If you run this code, you’ll see that the elements of a jQuery collection are nothing more or less than DOM elements. And the last test works just fine: wrapping a jQuery collection just returns the jQuery collection again (actually, it’s a duplicate of the first).

Let’s investigate the cd:each method, since it’s used so often to apply some changes to all of the elements of a jQuery collection.

Suppose we have this code:

$(".post").each(function (index, obj) {
  // what type is obj?
});

A few moments with the test functions I’ve already provided will convince you that it is a DOM element. (OK, agreed, you could have looked it up in the jQuery documentation or you could have puzzled out the code itself.) But as a DOM element we’re locked out of all the cool jQuery features like animations, and so on. We have to resort to some kludgey old-style code to set attributes, set event handlers, and so on. Or do we?

Take a look at the cd:init constructor once again. We see this code:

        // Handle $(DOMElement)
        if (selector.nodeType) {
          this.context = this[0] = selector;
          this.length = 1;
          return this;
        }

(selector is the first parameter in the call to $; in our case the DOM element.) As you can see it wraps the DOM element as a jQuery collection of length 1. This code is really high up in the init code, way before any selector-finding logic, so executes rapidly in practice. In other words, this works very well:

$(".post").each(function (index, obj) {
  $(obj).someCoolJQueryMethod();
});

Before we leave each, look at its last statement: it returns the object each was acting on. In fact this is a general feature of the jQuery methods: they all return the original collection they were called on. This is a cool feature: it actually means that we can chain calls to methods like this:

$(".post")
  .hide("slow")
  .each(function (index, obj) {
    $(obj).css("background-color", "teal");
  })
  .show("slow");

So: “find all elements of class ‘post’, hide them all, change the background color to teal on them all, and then show them all”. It avoids nasty code like this:

$(".post").hide("slow");
$(".post").each(function (index, obj) {
  $(obj).css("background-color", "teal");
});
$(".post").show("slow");

Actually this brings up another point. Suppose we have code like this:

$(".post").hide("slow");
// other code not changing the DOM structure
$(".post").show("slow");

Think about what this is doing: it has to go to the DOM and query it for all the elements of class “post” using the Sizzle code. No matter how you look at it, it takes a relatively long time. Far better is to save the jQuery collection in a local variable and reuse it:

var allPosts = $(".post");
allPosts.hide("slow");
// other code not changing the DOM structure
allPosts.show("slow");

And that’s it for now. Next time, on June 6, we’ll be looking at the basics of using jQuery in my next webinar. We’ve looked at the code, now it’s time to use it.

no comments
No Comments

Please login or register to post comments.