Blog

18 Surprises From Reading jQuery’s Source Code

Featured in JavaScript Weekly

I love jQuery, and though I consider myself an advanced JavaScript developer, I had never read the jQuery source from top to bottom, until now. Here are a few things that I learned along the way:

Note: I use the $.fn.method() syntax to refer to the practice of calling method on a set of matched elements. For example, when I say $.fn.addClass, that represents uses like $(‘div’).addClass(‘blue’) or $(‘a.active’).addClass(‘in-use’). $.fn is the prototype for jQuery-wrapped elements.

  1. Sizzle’s weight: Sizzle is the selector engine jQuery uses to find elements in a DOM based on CSS selectors. It’s what turns $(‘div.active’) into an array of elements you can operate on. I knew Sizzle made up a large portion of jQuery, but I was surprised at just how massive it really is. It’s easily the single biggest feature, line-count wise, in jQuery source. By my calculations it makes up 22% of the overall codebase. That dwarfs the next biggest feature in jQuery—$.ajax—which makes up just 8% of the library’s codebase.

  2. $.grep: This method is similar to Underscore’s _.filter in that it takes two arguments, an array of elements and a function, and returns the elements that pass the function’s truth test.

  3. Bubbling caveats: jQuery specifically prohibits one type of event from ever bubbling. That is the load event. Internally, jQuery passes a special noBubble: true flag through with any load events, so that image.load events can’t bubble up to the window (which could mistakenly match a window.load event).

  4. Default animation speed: jQuery animates elements by changing their style attributes in quick succession. Each of these changes is called a “tick.” The default animation speed is to run a tick every 13 milliseconds, and you can adjust this by overriding jQuery.fx.interval with your own integer.

  5. $.fn.addClass accepts a function: We usually supply $.fn.addClass with a string of class names to add to an element. But it can also accept a function. You must return a string of space-separated class names from this function to apply them to the matched element. As a bonus, this function receives the matched element’s index as an argument, which you can use to build intelligent class names.

  6. So does $.fn.removeClass: This also accepts a function, like the method mentioned above. This function also automatically receives the element’s index.

  7. :empty pseudo selector: This convenient pseudo selector will match elements with no children.

  8. :lt and :gt pseudo selectors: These pseudo selectors match elements based on their index in the matched set. For example, $(‘div:gt(2)’) will return all divs except for the first three (numbers are zero-indexed). If you supply a negative integer as the argument, it counts backwards starting from the end of the set.

  9. $(document).ready() uses a promise: jQuery eats its own dog food, it would seem. Internally, trusty ol’ $(document).ready() uses a jQuery deferred to determine when the DOM is fully loaded.

  10. $.type: I’m sure we’re all familiar with typeof to determine what data type something is, but did you know jQuery provides a .type() method? jQuery’s version is more intelligent than the native browser version. For example, typeof (new Number(3)) returns “object,” whereas $.type(new Number(3)) returns “number.” Update: As ShirtlessKirk pointed out in the comments, $.type returns the type of the .valueOf() property of the receiving object. So it’s more accurate to say that $.type tells you the type of the return value for an object.

  11. $.fn.queue: You can inspect an element’s effects queue with the following example code: $(‘div’).queue(). This is useful if you need to know how many effects are remaining on an element. Even more useful, you can directly manipulate the queue to add your own effects. From the jQuery docs:

  $( document.body ).click(function() {
    $( "div" )
      .show( "slow" )
      .animate({ left: "+=200" }, 2000 )
      .queue(function() {
        $( this ).addClass( "newcolor" ).dequeue();
      })
      .animate({ left: "-=200" }, 500 )
      .queue(function() {
        $( this ).removeClass( "newcolor" ).dequeue();
      })
      .slideUp();
  });
  1. Click events prohibited on disabled elements: jQuery automatically won’t process click events on disabled elements, a nice optimization to save you from having to write your code to check against this scenario.

  2. $.fn.on accepts an object: Did you know that $.fn.on accepts an object to attach multiple events at once? An example from the jQuery docs:

  $( "div.test" ).on({
    click: function() {
      $( this ).toggleClass( "active" );
    }, mouseenter: function() {
      $( this ).addClass( "inside" );
    }, mouseleave: function() {
      $( this ).removeClass( "inside" );
    }
  });
  1. $.camelCase: This utility method converts dashed-strings to camelCased strings.

  2. $.active: Calling $.active returns the number of active XHR queries. This can be useful for enforcing limits to how many in-flight AJAX requests you’ll allow at once.

  3. $.fn.parentsUntil / $.fn.nextUntil / $.fn.prevUntil: I was very familiar with the .parents(), .next(), and .prev() methods, but I didn’t know these other versions existed. Essentially, they’ll match all parents/next elements/previous elements until they encounter your stop-condition element.

  4. $.fn.clone arguments: When you .clone() an element, you can also clone its data attributes and events by passing true as the first argument to clone.

  5. More $.fn.clone arguments: In addition to the above, you can clone its children’s data attributes and events by passing an additional true argument. This is called a “deep clone.” That second argument defaults to the value of the first (whose default is false). So if the first is true and you want the second to be true, you can omit the second argument entirely.

An earlier version of this post said the return value of #5’s function argument should be a comma-separated string. It should actually return a space-separated string. Thanks Jonathan!