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

One thought on “JavaScript Context Confusion

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s