Web Development

JavaScript Context Confusion

Or: how I learned to stop crossing my fingers about this.

The mutability of this is one of the hardest things to understand when writing JavaScript (the other is prototypal inheritance). Callbacks make this especially confusing and easy to lose track of.

Here’s a fairly common (and over-simplified) pattern:

var module = {
  init: function() {
    // alias "this"
    var self = this;
    // attach event handlers
    $("#foo").on( 'click', function( event ) {
      // this.takeAction() would throw a TypeError since jQuery
      // makes "this" a DOM Element rather than "module"
      self.takeAction( event );
    });
  },
  takeAction: function( event ) {
    // I need to have (this === module) to work properly
  }
}

module.init();

The problem modular code runs into is that jQuery assigns this to be the DOM Element that was clicked on in the above example. This is good for writing typical jQuery spaghetti code, but not when we want more modular code. So we alias this to something like self or that to be used inside a nested function’s scope. ECMAScript 5 solves this problem with Function.prototype.bind, ensuring that a function is always called in a specified context:

var module = {
  init: function() {
    // attach event handlers
    $("#foo").on( 'click', this.takeAction.bind( this ) );
  },
  takeAction: function( event ) {
    // I need to have (this === module) to work properly
  }
}

// helpful here too!
$( document ).ready( module.init.bind( module ) );

Now, if you find yourself in a situation where you need contextual information from both the DOM Element and the this context bound via Function.prototype.bind, you’re probably doing it wrong. (And anyway, you can still grab it from event.target.)

It would be great if we could just code like this today, but Function.prototype.bind is only supported in IE >= 9. But this is a common problem, and most frameworks solve this in some manner.

// jQuery.proxy
var module = {
  init: function() {
    // attach event handlers
    $("#foo").on( 'click', $.proxy( this.takeAction, this ) );
  },
  takeAction: function( event ) {
    // I need to have (this === module) to work properly
  }
}

// _.bind
var module = {
  init: function() {
    // attach event handlers
    $("#foo").on( 'click', _.bind( this.takeAction, this ) );
  },
  takeAction: function( event ) {
    // I need to have (this === module) to work properly
  }
}

I would recommend, whenever possible, to use Underscore’s _.bind over jQuery.proxy, since the latter can create some oddness (see docs) when unbinding event handlers. Plus Underscore is awesome.

And now I have some old code to go cringe at.

Standard