The Power Of Closures - Deferred Object Bindings In jQuery 1.5

Over the weekend, I started to explore the Deferred objects that were added to jQuery 1.5. Deferred objects are stateful queues that can trigger success and fail event handlers. In a follow-up exploration, I wanted to see if I could use Deferred objects to power asynchronous script loading. As it turns out, you certainly can; but, the way in which I listened for the DOM-ready event should have raised some eyebrows.

Most of the asynchronous script loading that I wrote depends on the Promise objects returned from the $.ajax() method (which was completely rewritten in jQuery 1.5 to use Deferred objects). However, in addition to loading scripts, my asynchronous script loader also needed to wait for the DOM-ready event before it could use the loaded scripts.

In jQuery, we typically bind to the DOM-ready event by passing a function reference to the $() method:

$( function(){ ... DOM-ready code ... } );

Following this approach, I created a one-off Deferred object that would resolve when the DOM-ready event was fired:

$.Deferred( function( deferred ){ // In addition to the script loading, we also // want to make sure that the DOM is ready to // be interacted with. As such, resolve a // deferred object using the $() function to // denote that the DOM is ready. $( deferred.resolve ); } )

As you can see, the logical end of this code is the following line:

$( deferred.resolve );

Here, rather than passing a lambda (anonymous) function to the jQuery constructor ($), as we might normally, we are passing a reference to the resolve() method of the one-off Deferred object instance. Passing function references can be a bit tricky, though; in this case, we're not actually passing the function as a method on the "deferred" instance - we're simply passing the free-floating function reference that happens to be a property of the deferred object.

The binding of a function doesn't matter until the function is invoked. This is why functions can be copied from one object to another; this is also why native functions like call() and apply() exist in Javascript and why functions like $.proxy() exist in jQuery.

So, how is it that we can pass the resolve() function reference and have it act upon the intended Deferred object? The answer: Closures. If you look at the source code for the jQuery 1.5 Deferred (and the _Deferred) constructor, you'll notice that it explicitly returns an object. This is why the "new" keyword is optional - you're not getting the instantiated Deferred class instance, you're getting an explicitly created object literal.

Without digging into the jQuery 1.5 source code, I can quickly illustrate this concept with a small demo. In the following code, I create a Girl constructor; but, within the constructor, notice that I am explicitly creating and returning a local object:

<!DOCTYPE html> <html> <head> <title>Deferred Object Bindings in jQuery 1.5</title> <script type="text/javascript" src="../jquery-1.5.js"></script> <script type="text/javascript"> // Define the girl constructor. This returns a new Girl // instance, but not in the traditional sense. function Girl( name ){ // Create a girl singleton. var girl = { // Set the name property. name: name, // I say hello to the calling person. Notice that // when this method invokes properties, it calls // them on the local "girl" instance. This function // has created a closure with the local context and // therefore has access to the "girl" instance no // matter how this method is invoked. sayHello: function(){ return( "Hello, my name is " + girl.name + "." ); } }; // Return the girl instance. This will be different than // the actual instance created by the NEW constructor // called on the Girl class (though no references to the // NEW-based instance will be captured). return( girl ); } // -------------------------------------------------- // // -------------------------------------------------- // // Create some girl instances. var sarah = new Girl( "Sarah" ); var jilly = new Girl( "Jilly" ); // Collect the sayHello methods. By collecting the method // references, they are no longer bound to their original // context objects; the closure behavior of the functions, // however, remains in-tact. var methods = [ sarah.sayHello, jilly.sayHello ]; // Loop over the functions to execute them. $.each( methods, function( i, sayHello ){ // Execute the context-less function. console.log( sayHello() ); } ); </script> </head> <body> <!-- Left intentionally blank. --> </body> </html>

Once I've defined my constructor, I create two instances of the Girl class which, in turn, creates two instances of the "girl" literal. Then, I am gathering up the two sayHello() method references and executing them outside the context of their original binding. When I do this, I get the following console output:

Hello, my name is Sarah.

Hello, my name is Jilly.

As you can see, the methods, though invoked without their parent context, are still bound to the appropriate "name" values. This is possible because we're not actually relying on the object binding; rather, we're relying on the method's lexical binding - in other words, its role in creating a closure.

Closures are not the easiest things to wrap your head around, so I've tried to outline this particular case graphically:

Inside the Girl() constructor, our encapsulated "girl" object is defined within the local scope of the function. Then the sayHello() method is defined within constructor. As such, no matter how we pass the sayHello() function reference around, it will always have access to the "girl" object literal; the caveat to this, of course, being that the function cannot make use of the "this" keyword, which depends on the invocation context.

I find this to be particularly interesting because I've never actually returned anything other than "this" from a class constructor. To be honest, I don't think I was even aware that you could override the value returned from a class constructor; I assumed this was one of those things that was auto-wired by the language. Creating objects in this way adds overhead and method duplication; I have to assume that the jQuery team chose this approach specifically so that resolve() and reject() methods could be passed around without object binding. Very clever stuff!

Tweet This Fascinating post by @BenNadel - The Power Of Closures - Deferred Object Bindings In jQuery 1.5 Woot woot — you rock the party that rocks the body!







