The Chains That Bind

Ubiquity Thy Name is jQuery
jQuery is an extremely popular open source JavaScript library, dual licensed under the GPL and MIT licenses. Figure 1 shows the percentage use of jQuery among the top ten thousand, hundred thousand, and one millions websites on the internet as ranked by Alexa [!!]. Additionally the project's website sees more than one hundred thousand [!!] unique visits a month from developers all over the world. Given these numbers it's fair to say that jQuery is one of the most popular libraries available to JavaScript developers, and possibly one of the most popular open source libraries available to any developer.
This popularity is due in part to the fluent interface that the library promotes for manipulating the browser's document object model. As with many abstractions that simplify complex data manipulation it often requires performance tradeoffs. Here we identify a subset of chainable jQuery methods that can be optimized in the interest of increasing performance. As a result we derive a simple jQuery extension that can assist developers in identifying when these methods are being used.
Fluent Interface
In Figure 2 you can see an example of a typical DOM manipulation built using jQuery's chainable methods. In this case all the HTMLDivElement
s are selected from the DOM, hidden, the foo
CSS class is added, and they are shown again. Figure 3 shows a "deabstracted" naive implementation of the method chain from Figure 2 , and, as you can see, iterating over the set three times where one would seemingly suffice suggests an opportunity for a performance improvement.
Internally these methods are implemented using for loops or in one of two forms built with jQuery.map
and jQuery.each
.
jQuery( "div" ).hide().addClass( "foo" ).show();Figure 2
var $divs = $( "div" ); for( var i = 0; i < $divs.length; i++ ) { $divs[i].setAttribute( "style", "display: none;" ); } jQuery.each( $divs, function( element ) { element.setAttribute( "class", "foo" ); }); jQuery.map( $divs, function( element ) { element.setAttribute( "style", "display: block;" ); });Figure 3
Map Form
One of the two general forms that jQuery methods take is referred to here as the "map form". This leverages the jQuery helper methods jQuery.map
or jQuery.fn.map
to alter each element in the jQuery object set. This is the form with which our work is predominantly concerned due to the way it promotes operations on elements without side effects.
Each & For Forms
The other forms that one might find underlying jQuery methods are the "each form" and the "for form". Here side effects are the order of the day. Either of these forms can be implemented using jQuery.map
which provides further motivation in optimizing for the "map form". Figure 5 shows one such implementation
jQuery.map( list, function( elem, i ){ // manipulate HTMLElement return elem; });Figure 4
jQuery.each = function( list, callback ) { jQuery.map( list, function( elem, i ){ callback( elem, i); return elem; }); };Figure 5
When to Optimize?
Given that many of jQuery's core methods and the thriving plugin ecosystem that accompanies them leverage these forms [!! - possible list them in a figure] there is likely great value in identifying ways to reduce the full number of set iterations made when they are used in chains. Unfortunately the source that makes up these methods is often complex even from the implementors perspective so a rigorous definition of which can be targetted for optimization has value. For that purpose we turn to some rudimentary category theory.
jQuery Iterative Methods
TODO show some percentage of the jQuery api that is iterative. Pie chart?Categorically Identifiable

Defining Html
The Category Html is extremely simple. The objects in Html are the HTMLElements that the reader may be familiar with from the JavaScript DOM API (eg, HTMLDivElement). The morphisms are JavaScript functions from HTMLElements to to HTMLElements. Next we verify that Html satisfies the three category laws: identity, composition, and closure under composition (Figures 6,7,8).

Defining Jqry
Jqry is the category of jQuery
objects and functions from jQuery
objects to jQuery
objects. It's only slightly more complex than Html because we take this
to be an implicit parameter to the JavaScript functions that are the category’s morphisms. Also, these morphisms must be defined on the jQuery.fn
object to guarantee the value of this
is a jQuery
object.
function id( a ) { return a; }Figure 6
function cmps( f, g ){ return function( a ) { return f(g(a)); }; }Figure 7
$.fn.id = function(){ return this; };Figure 9
$.cmps = function( f, g ){ return function(){ return f.apply(g.apply(this)) }; };Figure 10
The identity function is trivial and function composition is always associative. We know that the morphisms in Html are closed under composition because the functions accept as their only argument HTMLElements and return only HTMLElements. Having met the three requirements for a category with Html we can move on to the second, and more complex category Jqry.

The value of this
, and the constraint that the morphisms must be defined on jQuery.fn
, play an important role in the way that the morphisms of Jqry behave. The prototype of all jQuery objects is $.fn
, meaning when you call $("div").foo()
it finds foo
on the $.fn
by following the prototype chain.
function a( elem ){ elem.setAttribute( "foo", "bar" ); return elem; } function b( elem ){ elem.setAttribute( "baz", "bak" ); return elem; } var elem = document.getElementById( "sample" ); elem = cmps( a, b )( elem ); elem.getAttribute( "foo" ); // "bar" elem.getAttribute( "baz" ); // "bak"Figure 8
$.fn.a = function(){ // a from Figure 8 return jQuery.map( this, a ); }; $.fn.b = function(){ // b from Figure 8 return jQuery.map( this, b ); }; var $elem = $( "#sample" ); $.fn.aAndB = $.cmps( $.fn.a, $.fn.b ); $elem.aAndB().attr( "foo" ); // "bar" $elem.attr( "baz" ); // "bak"Figure 11
Notice the use of a
and b
from the Html cmps
example in Figure 8 for the functions that are mapped using jQuery.map
over the set of elements in the jQuery objects in Figure 11 . This plays an important roll in understanding the Functor from Html to Jqry.

Composition works by taking the first function, g
, in Figure 10 and explicitly defining its this
value using the apply
method. The return value is a jQuery object which is used to define this
in the application of f
. As a result, we know that the morphisms of Jqry are closed under composition because each accepts and returns only jQuery objects.
Defining a Functor
The benefits of providing these two categories are realized in a Functor from Html to Jqry. jQuery's constructor, nearly always aliased as $
, transports the objects from Html ( Figure 12 ) and jQuery.map
partially applied to this
transports the morphisms ( Figure 13 ).
var htmlObject = document.getElementById( "sample" ); var jqryObject = $( htmlObject );Figure 12
function F( htmlMorphism ) { return function() { return jQuery.map( this, htmlMorphism ); }; } // a from Figure 8 $.fn.a = F(a); jqryObject.a().attr( "foo" ); // barFigure 13
Fuse with Confidence
The fact that the functor distributes over composition (Figure 14) suggests that jQuery methods using jQuery.map
with composed Html morphisms can be relied upon to behave identically to chains of their transported Jqry morphism counterparts. The reader may recognize this simply as loop fusion. With a rigorous understanding of which Jqry morphisms can be fused we can begin to define a standard and helper libraries to assist the end user in optimizing their jQuery method chains.
cmps(F(a), F(b)) == F(cmps(a, b)); $.cmps($.fn.a, $.fn.b) == F(cmps(a, b)); jqryObject.b().a() == jQuery.map(jqryObject, cmps(a, b));Figure 14

Automatically Warning the User
A Simple Standard
The proposed optimization requires that the Html morphism underlying each morphism in Jqry is available for fusion. Given the possible complexity of those morphisms the onus should be on the developers creating them to decide when and where to provide them. That could be a core developer, a plugin author, or just a developer abstracting common code to a single jQuery method within a project. Either way it's important for developers building new chainable jQuery methods to know and participate in the process. To that end we propose a simple standard:
All jQuery methods should, wherever possible, be built using the jQuery.map
method.
In addition to setting the groundwork for providing the underlying Html morphism this enforces consistency and readability so that when the morphism isn't provided seperately end users can attempt to provide their own. This should not come at the sacrifice of method performance. Methods using for and each forms can still provide a morphism for composition.
Methods defined with jQuery.map
should expose an Html morphism as an attribute.
Providing a consistent property makes fusion easier for the end user and enables automatic tooling like the sample identification library detailed below. We propose the composable
attribute as the consistent reference location where the morphism can be exposed.
Performance should be measured when converting jQuery methods to the map form.
Developers should verify, when converting jQuery methods from the for and each forms that there isn't a performance degredation. This will guarante ethat any theoretical speed benefits garnered from composing the Html morphisms won't be negated by the conversion.
Stuck in the Middle
Figure 15 shows the default prototype chain for jQuery objects. In equation 1 the default prototype lookup is altered to mirror equation 2 where the proxy object "captures" the prototype lookup (sigma f) by defining all the function properties on jQuery.fn
. When this happens the proxy can record the number of invocations made that could be optimized (Jqry morphisms) and at some threshold warn the user.

Sample Implementation
link to github source, mention automatic optimization attempt, warning only split method calls, drop in include
jQuery.fn.init = function(selector, context, rootjQuery){ // create a new proxy for each jQuery Object jQuery.fn.init.prototype = new jQuery.WarningProxy(); // instantiate the new jQuery object as normal return new jQuery.fn.init(selector, context, rootjQuery); };
Further Work
Closure compiler optimization using advanced mode, testing on large jQuery based projects