July 18th, 2007

Understanding Scope and Binding in JavaScript

11 comments on 1649 words

At the heart of binding, it’s merely a means to control execution scope—Function x executions in the scope of object y. It can be tough to grasp at first, but with the right amount of ninja references, anything can be explained so someone can understand it.

What’s my name fool

To get a basic understanding of binding, check out this example.


var Car = function() { this.name = 'car'; }
var Truck = function() { this.name = 'truck'; }

var func = function() { alert(this.name); }

var c = new Car();
var t = new Truck();

func.apply(c);
func.apply(t);

You can pop this in Firebug and you should get two alerts with car and truck respectively. To understand why this works, you need to understand a few basic things. Functions are objects in JavaScript, and apply allows you to apply the method of one object in another object, which basically means your controlling the execution scope.

You might say that func belongs to no object, but you’d be wrong, it belongs to the window object. You could envision that all functions that aren’t scoped within a visible object (meaning one written in front of your face) can be viewed as something like this:


// Don't ever write code like this, it's just an example to help you see things clearly
function window() {
  this.func = function() { alert(this.name); }
}

Hopefully this graphic will hammer the point home.

Execution scope

For the Ruby Folk

While I’ve never had to write anything like this in Ruby, it could be done in much the same fashion.


class Car
 attr_accessor :name
 def initialize
   @name = 'car'
  end
end

class Truck
  attr_accessor :name
  def initialize
    @name = 'truck'
  end
end

def func
  puts @name
end

c = Car.new
t = Truck.new

eval "func", c.send(:binding)

Prototype and those damn blocks

A common cause of confusion amongst developers using Prototype is binding, specifically in blocks and events— the two primary places where binding is used within Prototype and JavaScript written using Prototype. Take a look at the example below.


 var Ninja = Class.create();
 Ninja.prototype = {
   initialize: function(abilities) {
     this.abilities = [
      'Kick you in the face',
      'Rip out your spleen'
     ];

     this.abilities.each(function(ability) {
       this.executeAbility(ability)
     });
   },

   executeAbility: function(ability) {
     console.log(ability);
   }
 }

// Notice this function has the same name as a method on our Ninja class.
 function executeAbility(ability) {
   console.log("I was called from the window object:" + ability);
 }

 new Ninja();

Give this interesting piece of JavaScript a run in Firebug. This should print two lines to the console that were printed from the window object. The reason this happens is because this inside the anonymous function passed to each is the window object, not our class. This is obviously not what we want, so lets use bind so we can control the execution scope. Add bind to your code like the example below and run it again.


this.abilities.each(function(ability) {
  this.executeAbility(ability)
}.bind(this));

When you execute the code above, the executeAbility from within our class should be executed. This is because we bound the execution of executeAbility to our Ninja class. Because you can never have enough visual aids, below is an overview of this in color. Prototype binding

Binding and Events

In PAJ™ (Plain Ass JavaScript) , this inside a callback is actually the element which invoked the event, so it’s fairly easy to do the code like below.


  var ninja = document.getElementById('ninja');
  ninja.addEventListener('click', function () {
    alert(this.tagName);
  }, false);

However, in Prototype this points to the window (Update: In > 1.6 this is now the element) object inside a callback so we need to use bind in order to bind it to the object the method belongs to.


var Ninja = Class.create();
Ninja.prototype = {
  ...

  addObservers: function() {
   $('item').observe('click', this.kickSomeone.bindAsEventListener(this));
  },

  kickSomeone: function(event) {
    // Works because `this` is the Ninja instance
    // Without binding it would be the window
    this.someOtherMove(); 
  }

 ...

}

The only real difference between bind and bindAsEventListener is that bindAsEventListener ensures the event object is passed as the first argument.

Bonus Mojo: Early Binding

Sometimes you need to pass an additional argument to a callback function, and you need the value of the argument at the time it was bound. To understand what I’m talking about, check out this example:


  var phrase = "This is SPAARRTTAAAA!";

  $('somelink').observe('click', sayIt);

  function sayIt(event) {
    console.log(phrase);
  }

  phrase = "Red sauce on PAASTAAAA!"

When somelink is clicked, you might think the value of phrase would be ”This is SPAARRTTAAAA!”, but this isn’t true. Even though the value of phrase is ”This is SPAARRTTAAAA!” at the time the event listener is registered, it’s value is changed before the event fires. So, what you actually get printed to the console is ”Red sauce on PAASTAAAA!”. Not quiet the dramatic effect we wanted. To correct this, we can pass the value of phrase to the callback at the time the binding was created.


  var phrase = "This is SPAARRTTAAAA!";

  $('somelink').observe('click', sayIt.bindAsEventListener(this, phrase));

  function sayIt(event, phrase) {
    console.log(phrase);
  }

  phrase = "Red sauce on PAASTAAAA!";

Now, even though the value of phrase has changed later on in our code, we’ll actually get ”This is SPAARRTTAAAA!” because we bound the value at runtime instead of execution time.

Wrapping up

As you can see, binding isn’t really that confusing, it’s just hard to explain in a way people can understand it. Hopefully I achieved that, but if I didn’t I’ll chalk it up to not enough ninja references. For some further reading check out:

Discussion

  1. Nicolás Sanguinetti Nicolás Sanguinetti said on July 18th

    Nice writeup!

    You have a small typo on the first picture /* func.apply© where it should be func.apply(t) */, but other than that it was a nice explanation.

    Of the new additions on Prototype to Function I’m giving the prize to curry, by far the best one, with methodize a distant second :)

  2. kourge kourge said on July 18th

    Nice article, hopefully, more people will be able to grasp the concept of scopes and binding, since it’s an important aspect of JavaScript. It’s also part of what makes JavaScript so powerful.

  3. Andrew Dupont Andrew Dupont said on July 18th

    You’ve reminded me that I need to correct the Prototype docs — bindAsEventListener doesn’t need to be used unless you’re assigning an event the old-fashioned way (e.g., foo.onclick = ...). When you’re using Event.observe, bind will suffice.

  4. Justin Palmer Justin Palmer said on July 18th

    Thanks for the correction Nicolás. One fresh graphic coming up.

    @andrew: I’m glad you cleared that up. I never fully understood where bindAsEventListener would perform different than bind.

  5. topfunky topfunky said on July 18th

    I love the use of color in your diagrams…it makes much more sense that way.

    Now we need a text editor with similarly color-coded scopes!

  6. benvds benvds said on July 19th

    Really great article. I ran into the bind problem yesterday and couldn’t find a solution to it, so your timing couldn’t be better :)

    It takes a while learning javascript but I’m getting there. Mainly thanks to Prototype and the JS communty. Thx guys.

  7. Frank Rosario Frank Rosario said on July 20th

    Terrific write up, very useful. Definitely going into my del.icio.us bookmarks….

  8. Herry Herry said on July 22nd

    Impressive writeup, binding is a tough subject to understand, never seen it written so clear and concise before. Thanks!

  9. Travis Hensgen Travis Hensgen said on July 23rd

    Nice post Justin – more people need to understand binding since it’s a crucial first step in being able to incredible things with Prototype.

  10. Taree Taree said on July 26th

    Thanks Justin.

    I’ve never grasped Binding properly which does let down alot of the functionality of Javascript. However, i’ve never seen it presented so simply and a light bulb has just gone off inside the old “noggen” so to speak.

    I’m doing some experimentation using your examples, and this is the most success i’ve ever had using binding.

    Much appreciated, Carly!

  11. Casey Watson Casey Watson said on July 31st

    Thanks for the great article. I have been looking for how to do this for quite a while. I’ll add that this technique is very useful for handling complex events THE RIGHT WAY.

Sorry, comments are closed for this article.