Google

[Update] Thanks to Charlie in the comments for pointing me to jQuery's $.fn.proxy method.

Developers have traditionally used JavaScript for relatively simple DOM manipulations and XHR, but as more functionality moves to the client, the techniques used by those developers have been slow to evolve. One consequence of this slow evolution is systems comprised entirely of jQuery and a series of nested event bindings. While jQuery is an extremely effective browser API, hiding the clumsy and often bug ridden out of the box equivelants, developers need effective solutions for organizing and reusing their code.

Nested Closures

Relegating code to event bindings breaks down in even very simple situations for a few important reasons. First the state that's closed over is often hard to track and bindings can easily be shadowed.

(function( $ ) {
  function other() {
    // ...
  }

  function header() {
    //...
  }

  $(".bar").click(function() {
    var header = $( "header" );

    // ...
  });
})( jQuery );

Developers altering the click callback are required to know all the bindings in the current and parent scopes or face the possibility of attempting something unfortunate like using the invocation operator on a jQuery object. Second the inclusion of the state in each callback is implicit which means everything in the enclosing scope is carried with the callback. This is the beauty of the closure when used properly, but here it only adds complexity and possibly memory bloat. Third, and most importantly, the code tied up in the functions and callbacks is entirely unusable outside that context.

Objects Not Functions

As far as alternatives go, it is possible to write JavaScript in a functional style. Sadly JavaScript lacks many language features that make functional programming easy and elegant. Partial application, pattern matching, tail call optimization, and simple composition are all missing. It's also a style of programming that is unfamiliar to the vast majority of front end developers. For now objects appear to be the best means of code reuse and organization where JavaScript is concerned.

With that in mind there are a number of options worth considering. Many JavaScript libraries provide a class system to emulate similar systems in other languages. MooTools is a prime example in that it's Class constructor supports classical inheritance, mixins, and more. This approach has the obvious benefit of being relatively familiar to developers without a lot of JavaScript experience and if you prefer to work with the DOM via jQuery you can even discard the MooTools DOM API with the download builder.

Additionally a lot of client side software involves state management between JavaScript objects and some DOM representation. Libraries like Backbone and Sproutcore provide models in one form or another that can act as a class replacement. As Yehuda Katz pointed out in his presentation "Getting Truth Out of the DOM", attempting to manage complex state between the the DOM and JavaScript is best left to an existing solution. Taken with the need to better organize an application these libraries can be invaluable.

Finally, you can use the (gasp) prototype system provided by JavaScript. The aversion to JavaScript's object model appears to be an unfortunate side effect of the overall lack of experience with the language, but by creating a constructor and then defining methods on it's prototype you can group, organize, and reuse common functionality quite easily1. Even when choosing another solution, neglecting to understand JavaScript at this level is an unfortunate misstep.

Operating under the assumption that jQuery will remain the primary means of interacting with the DOM for most developers it's necessary to address binding context (ie this) as it becomes necessary when using object methods as arguments to higher order functions (eg event handlers).

jQuery and Function.prototype.bind

One conspicuous absence from jQuery is a helper for function context binding jQuery's answer for context binding is the $.proxy method. That is, it provides a way to guarantee the value of this during the invocation of a given function. this becomes important very quickly in even simple attempts to reconcile DOM events with code organization through objects. Here we'll build a library agnostic version.

function AlertArea( $domElement ){
  // bind to the instances onCloseClick
  $domElement.find( "div.close" ).click(this.onCloseClick);

  // if the alert area has text on creation show it
  if( $domElement.text() ) {
    $domElement.show();
  }

  // track the dom element for
  this.$domElement = $domElement;
}

AlertArea.prototype.onCloseClick = function() {
  // BOOM!!!
  this.$domElement.hide();
};
In this example when the constructor is invoked with some jQuery wrapped DOM object, if that object has text, it's displayed. It also binds the prototype method onCloseClick to clicks on child elements matching the div.close selector. The problem here is that this in onCloseClick won't be the AlertArea object when the click event is triggered but rather the DOM element on which the event was triggered (a feature of jQuery). To guarantee the this value upon invocation a binding function is required.
function bindContext(newThis, fn) {
  return function() {
    fn.apply(newThis, arguments);
  };
}
This function takes as arguments a context (newThis) and a function (fn). It then creates a new function that, when invoked, will use the apply method of the original function to invoke it with the new context. More commonly this is defined on the function prototype.
Function.prototype.bind = function(newThis) {
  var self = this;

  return function() {
    self.apply(newThis, arguments);
  };
};
With that in place the this value of the onCloseClick method can be guaranteed by altering the click binding
// bind to the instances onCloseClick

$domElement.find( "a.close" ).click(this.onCloseClick.bind(this));

The fact that jQuery lacks this core function is telling. It isn't attempting to facilitate your code organization because that's not what it's for. I like to think that at least part of jQuery's success is due to it's focus on a very specific set of problems, but that leaves the developer to choose their preferred method of code organization. Sadly many developers fail to take that step though it can easily be accomplished with JavaScript's prototype system.

Getting Comfortable With this

A simple library for managing textareas should serve to illustrate how the prototype system is an easy win over nested closures both early on and as application complexity grows. We'll start with the more common approach:

$( "textarea" ).each(function(i, elem) {
  var $textArea = $(elem);

  function grow(){
    var scrollHeight = $textArea[ 0 ].scrollHeight,
        clientHeight = $textArea[ 0 ].clientHeight;

    if ( clientHeight < scrollHeight ) {
      $textArea.height(scrollHeight + 20);
    }
  }

  function limitWarn() {
    if( $textArea.val().length > 10 ) {
      $textArea.css( 'background-color', 'red' );
    }
  }

  $textArea.keyup(grow).keyup(limitWarn);
  $(window).load(grow).load(limitWarn);
});

For all textarea elements in the DOM the grow and limitWarn functions are bound to keyup for user input and $(window).load for text included in the markup. grow does a simple calculation to determine if the text exceeds the textarea's rendered height and then increases it where necessary. limitWarn changes the background color of the textarea when the content length exceeds a given threshold. Already there are three references bleeding into grow and limitWarn (ie, i, elem, and limitWarn/grow). Now, the equivalent using the prototype system.

function TextArea( $textArea, limit ){
  var grow = this.grow.bind(this),
      limitWarn = this.limitWarn.bind(this);

  $textArea.keyup(grow).keyup(limitWarn);
  $(window).load(grow).load(limitWarn);

  this.$textArea = $textArea;
  this.limit = limit;
}

TextArea.prototype.grow = function() {
  var scrollHeight = this.$textArea[ 0 ].scrollHeight,
      clientHeight = this.$textArea[ 0 ].clientHeight;

  if ( clientHeight < scrollHeight ) {
    this.$textArea.height(scrollHeight + 20);
  }
};

TextArea.prototype.limitWarn = function() {
  if( this.$textArea.val().length > this.limit ) {
    this.$textArea.css( 'background-color', 'red' );
  }
};

$("textarea").each(function(i, elem) {
  new TextArea( $(elem), 10 );
});

The only additional overhead is the function context bindings2, typing TextArea and prototype, and the instantiation. The upshot is a purpose built constructor where the each closure served before and a very specific portal through which state contained in the object is accessed (this).

Now consider another developer working on the same project needs to manage textareas in a different context with a minor alteration. The closure based solution requires something of an overhaul, but the TextArea prototype can remain nearly untouched while it's repurposed elsewhere.

function DialogWarnTextArea( $textArea, limit, $dialog ) {
  TextArea.apply(this, arguments);

  this.$dialog = $dialog;
}

DialogWarnTextArea.prototype = new TextArea();

DialogWarnTextArea.prototype.limitWarn = function() {
  if( this.$textArea.val().length > this.limit ) {
    this.$dialog.text("Too much text!").show();
  }
};

A few things to take note of. First, an application of the original TextArea constructor to this in the DialogWarnTextArea constructor makes sure the original attributes are set up properly (ie limit and $textArea). You can think of this as an explicit super.

TextArea.apply(this, arguments);

Second, assigning the prototype to a new instance of TextArea places the methods and attributes defined on it's prototype in the chain above those defined (later) on the DialogWarnTextArea prototype. This is referred to by most JavaScript developer's as "prototypal inheritance" for the way it emulates method/attribute lookup in languages with classical inheritance3.

DialogWarnTextArea.prototype = new TextArea();

Third, defining a limitWarn attribute on the DialogWarnTextArea prototype means that objects created with the DialogWarnTextArea will use that definition since it appears earlier in the prototype chain.

<pre>

DialogWarnTextArea.prototype.limitWarn = function() { if( this.$textArea.val().length > this.limit ) { this.$dialog.text("Too much text!").show(); } };

Last, we need to handle the case where the TextArea constructor is invoked with the new operator to set a prototype (ie, without arguments) which constitutes our only alteration to the TextArea code.

function TextArea( $textArea, limit ){
if( !arguments.length ) return;
// ...
}

It should also be pointed out that there are a few items worth refactoring (3 arg and the limitWarn boolean operation) but the changes are at least palatable and mostly non-invasive. In stark contrast, achieving the same functionality without the prototype system requires moving the grow function into a parent scope, gathering the target from the event object passed to grow as an event handler, and creating another each loop over the second set of textareas.

function grow( event ){
var $textArea = $(event.target),
scrollHeight = $textArea[ 0 ].scrollHeight,
clientHeight = $textArea[ 0 ].clientHeight;
if ( clientHeight < scrollHeight ) {
$textArea.height(scrollHeight + 20);
}
}
$( "textarea.other" ).each(function( i, elem ) {
var $textArea = $(elem);
function limitWarn() {
if( $textArea.val().length > 10 ) {
$("#dialog").text("Too much text!").show();
}
}
$textArea.keyup(grow).keyup(limitWarn);
$(window).load(grow).load(limitWarn);
});

Also unfortunate is that grow has lost context for the reader, it relies entirely on the fact that the event.target will behave like a textarea. Ultimately this is really about getting any reusability at all with the added bonuses of sane state access and a limit on the enclosed bindings. Using closures alone to manage scoping concerns simply collapses where reuse is concerned.

Slow Progress

My fear, and the motivation for this post, is that developers are not turning quickly enough to libraries and language constructs, be they MV* frameworks or plain old prototypes, which are crucial to building more complex client side applications. For nearly any application that must be maintained, even those with a minimum of client side code, developer's should look to move beyond the simple scoping semantics of the jQuery event callback and leverage the code organization and reuse features that JavaScript and its libraries have to offer.

  1. One of the best examples I've found is Google's closure library. Whatever you may think of the library itself, the code is really nice to look through.
  2. Underscore.js provides a nice facility for binding all methods on an object to that object context (_.bindAll), though it requires explicit inclusion of methods defined on the prototype
  3. You can learn more about prototypal inheritance from Douglas Crockford and Yehuda Katz

Published

25 Nov 2011

Tags