Understanding bind and bindAll in Backbone.js

Backbone.js users use bind and bindAll methods provide by underscore.js a lot. In this blog I am going to discuss why these methods are needed and how it all works.

It all starts with apply

Function bindAll internally uses bind . And bind internally uses apply. So it is important to understand what apply does.

var func = function beautiful(){
  alert(this + ' is beautiful');
};
func();

If I execute above code then I get [object window] is beautiful. I am getting that message because when function is invoked then this is window, the default global object.

In order to change the value of this we can make use of method apply as given below.

var func = function beautiful(){
  alert(this + ' is beautiful');
};
func.apply('Internet');

In the above case the alert message will be Internet is beautiful . Similarly following code will produce Beach is beautiful .

var func = function beautiful(){
  alert(this + ' is beautiful');
};
func.apply('Beach'); //Beach is beautiful

In short, apply lets us control the value of this when the function is invoked.

Why bind is needed

In order to understand why bind method is needed first let's look at following example.

function Developer(skill) {
  this.skill = skill;
  this.says = function(){
    alert(this.skill + ' rocks!');
  }
}
var john = new Developer('Ruby');
john.says(); //Ruby rocks!

Above example is pretty straight forward. john is an instance of Developer and when says function is invoked then we get the right alert message.

Notice that when we invoked says we invoked like this john.says(). If we just want to get hold of the function that is returned by says then we need to do john.says. So the above code could be broken down to following code.

function Developer(skill) {
  this.skill = skill;
  this.says = function(){
    alert(this.skill + ' rocks!');
  }
}
var john = new Developer('Ruby');
var func = john.says;
func();// undefined rocks!

Above code is similar to the code above it. All we have done is to store the function in a variable called func. If we invoke this function then we should get the alert message we expected. However if we run this code then the alert message will be undefined rocks!.

We are getting undefined rocks! because in this case func is being invoked in the global context. this is pointing to global object called window when the function is executed. And window does not have any attribute called skill . Hence the output of this.skill is undefined.

Earlier we saw that using apply we can fix the problem arising out of this. So lets try to use apply to fix it.

function Developer(skill) {
  this.skill = skill;
  this.says = function(){
    alert(this.skill + ' rocks!');
  }
}
var john = new Developer('Ruby');
var func = john.says;
func.apply(john);

Above code fixes our problem. This time the alert message we got was Ruby rocks!. However there is an issue and it is a big one.

In JavaScript world functions are first class citizens. The reason why we create function is so that we can easily pass it around. In the above case we created a function called func. However along with the function func now we need to keep passing john around. That is not a good thing. Secondly the responsibility of rightly invoking this function has been shifted from the function creator to the function consumer. That's not a good API.

We should try to create functions which can easily be called by the consumers of the function. This is where bind comes in.

How bind solves the problem

First lets see how using bind solves the problem.

function Developer(skill) {
  this.skill = skill;
  this.says = function(){
    alert(this.skill + ' rocks!');
  }
}
var john = new Developer('Ruby');
var func = _.bind(john.says, john);
func();// Ruby rocks!

To solve the problem regarding this issue we need a function that is already mapped to john so that we do not need to keep carrying john around. That's precisly what bind does. It returns a new function and this new function has this bound to the value that we provide.

Here is a snippet of code from bind method

return function() {
  return func.apply(obj, args.concat(slice.call(arguments)));
};

As you can see bind internally uses apply to set this to the second parameter we passed while invoking bind.

Notice that bind does not change existing function. It returns a new function and that new function should be used.

How bindAll solves the problem

Instead of bind we can also use bindAll . Here is solution with bindAll.

function Developer(skill) {
  this.skill = skill;
  this.says = function(){
    alert(this.skill + ' rocks!');
  }
}
var john = new Developer('Ruby');
_.bindAll(john, 'says');
var func = john.says;
func(); //Ruby rocks!

Above code is similar to bind solution but there are some big differences.

The first big difference is that we do not have to worry about the returned value of bindAll . In case of bind we must use the returned function. In bindAll we do not have to worry about the returned value but it comes with a price. bindAll actually mutates the function. What does that mean.

See john object has an attribute called says which returns a function . bindAll goes and changes the attribute says so that when it returns a function, that function is already bound to john.

Here is a snippet of code from bindAll method.

function(f) { obj[f] = _.bind(obj[f], obj); }

Notice that bindAll internally calls bind and it overrides the existing attribute with the function returned by bind.

The other difference between bind and bindAll is that in bind first paramter is a function john.says and the second parameter is the value of this john. In bindAll first paramter is value of this john and the second parameter is not a function but the attribute name.

Things to watch out for

While developing a Backbone.js application someone had code like this

window.ProductView = Backbone.View.extend({
  initialize: function() {
    _.bind(this.render, this);
    this.model.bind('change', this.render);
  }
});

Above code will not work because the returned value of bind is not being used. The correct usage will be

window.ProductView = Backbone.View.extend({
  initialize: function() {
    this.model.bind('change', _.bind(this.render, this));
  }
});

Or you can use bindAll as given below.

window.ProductView = Backbone.View.extend({
  initialize: function() {
    _.bindAll(this, this.render);
    this.model.bind('change', this.render);
  }
});