Understanding bind and bindAll in Backbone.js
- Transfer
Backbone.js users often use the bind and bindAll methods provided by the Underscore.js library. In this blog, I am going to discuss why these methods are needed and how they work.
The bindAll function uses bind internally. And bind in turn uses apply. Therefore, it is important to understand what apply does.
If I execute the above written code, then I will get "[object window] is beautiful". I get this message because when the function is called, this is equal to window, the default global object.
In order to change the value of this, we can use the apply method as shown below.
In the above case, the message “Internet is beautiful” will be displayed. Similarly, the following code will produce “Beach is beautiful”.
In short, apply allows us to control the value of this when a function is called.
In order to understand why the bind method is needed, first let's look at the following example.
The example above is pretty straight forward. The john object is an instance of Developer, and when the says function is called, we get the correct warning message.
Notice that when we call says, we call it as follows john.says (). If we just want to get hold of the function that says returns, then john.says needs to be done. Thus, the code above can be broken by the following overview.
The code above is similar to the code above it. All we did was save the function into a variable called func. If we call this function, then we should get the message that we are expecting. However, if we run this code, the warning message will be “undefined rocks!”.
We get “undefined rocks!” Because in this case the func function was called in a global context. When the function is executed, this points to a global object called window. And the window object has no property called skill. So the output value of this.skill is undefined.
We saw earlier that using apply, we can solve the problem arising from this. So let's try to use apply to solve it.
The code above solves our problem. This time, the notification message we received was "Ruby rocks!" However, there is a problem and rather big one.
In the JavaScript world, functions are first class objects. The reason we create the function is because we can easily pass it everywhere. In the case described above, we created a function called func. However, along with the func function, we must now pass the john variable everywhere. This is not a good idea. Secondly, the responsibility for the correct call of this function was transferred from the creator function to the consumer function. This is not a very good API.
We should try to create functions that can be easily called by consumers. And here bind comes into play.
First, let's see how using bind solves the problem.
To solve the problem concerning this, we need a function that is already mapped to john in such a way that we do not have to take care of it everywhere. This is exactly what the bind method does. It returns a new function and its this value becomes the one we provided.
Here is a piece of code from the bind method:
As you can see inside, bind uses apply to set this to the second parameter that we passed when we called bind.
Note that bind does not modify an existing function. It returns a new function and you need to use it.
Instead of bind, we can also use bindAll. Here is a solution with bindAll.
The code above is similar to a solution with bind, but there are a few big differences. The first difference is that we don’t have to worry about the return value of bindAll. In the case of bind, we must use the return function. In bindAll, we don’t worry about the return value, but you have to pay a price for it. In fact, bindAll modifies the function. What does it mean?
See that the john object has a property called says that returns a function. The bindAll method changes the says property so that when it returns a function, it is already associated with the john object.
Here is the code snippet from the bindAll method:
Note that bindAll internally calls the bind method and it replaces the value of the existing property with the function that bind returned.
Another difference between bind and bindAll is that in bind, the first parameter is the john.says function, and the second is the john object. In bindAll, the first parameter is the john object and the second parameter is not a function, but the name of the property.
When developing Backbone.js applications, someone wrote the code as follows:
The code above will not work because the return value of bind is not used. Proper use will be as follows:
Or you can use bindAll as shown below:
It all starts with apply
The bindAll function uses bind internally. And bind in turn uses apply. Therefore, it is important to understand what apply does.
var func = function beautiful(){
alert(this + ' is beautiful');
};
func();
If I execute the above written code, then I will get "[object window] is beautiful". I get this message because when the function is called, this is equal to window, the default global object.
In order to change the value of this, we can use the apply method as shown below.
var func = function beautiful(){
alert(this + ' is beautiful');
};
func.apply('Internet');
In the above case, the message “Internet is beautiful” will be displayed. Similarly, the following code will produce “Beach is beautiful”.
var func = function beautiful(){
alert(this + ' is beautiful');
};
func.apply('Beach'); //Beach is beautiful
In short, apply allows us to control the value of this when a function is called.
Why bind needed
In order to understand why the bind method is needed, first let's look at the following example.
function Developer(skill) {
this.skill = skill;
this.says = function(){
alert(this.skill + ' rocks!');
}
}
var john = new Developer('Ruby');
john.says(); //Ruby rocks!
The example above is pretty straight forward. The john object is an instance of Developer, and when the says function is called, we get the correct warning message.
Notice that when we call says, we call it as follows john.says (). If we just want to get hold of the function that says returns, then john.says needs to be done. Thus, the code above can be broken by the following overview.
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!
The code above is similar to the code above it. All we did was save the function into a variable called func. If we call this function, then we should get the message that we are expecting. However, if we run this code, the warning message will be “undefined rocks!”.
We get “undefined rocks!” Because in this case the func function was called in a global context. When the function is executed, this points to a global object called window. And the window object has no property called skill. So the output value of this.skill is undefined.
We saw earlier that using apply, we can solve the problem arising from this. So let's try to use apply to solve 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);
The code above solves our problem. This time, the notification message we received was "Ruby rocks!" However, there is a problem and rather big one.
In the JavaScript world, functions are first class objects. The reason we create the function is because we can easily pass it everywhere. In the case described above, we created a function called func. However, along with the func function, we must now pass the john variable everywhere. This is not a good idea. Secondly, the responsibility for the correct call of this function was transferred from the creator function to the consumer function. This is not a very good API.
We should try to create functions that can be easily called by consumers. And here bind comes into play.
How bind solves a problem
First, let's 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 concerning this, we need a function that is already mapped to john in such a way that we do not have to take care of it everywhere. This is exactly what the bind method does. It returns a new function and its this value becomes the one we provided.
Here is a piece of code from the bind method:
return function() {
return func.apply(obj, args.concat(slice.call(arguments)));
};
As you can see inside, bind uses apply to set this to the second parameter that we passed when we called bind.
Note that bind does not modify an existing function. It returns a new function and you need to use it.
How bindAll solves a problem
Instead of bind, we can also use bindAll. Here is a 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!
The code above is similar to a solution with bind, but there are a few big differences. The first difference is that we don’t have to worry about the return value of bindAll. In the case of bind, we must use the return function. In bindAll, we don’t worry about the return value, but you have to pay a price for it. In fact, bindAll modifies the function. What does it mean?
See that the john object has a property called says that returns a function. The bindAll method changes the says property so that when it returns a function, it is already associated with the john object.
Here is the code snippet from the bindAll method:
function(f) { obj[f] = _.bind(obj[f], obj); }
Note that bindAll internally calls the bind method and it replaces the value of the existing property with the function that bind returned.
Another difference between bind and bindAll is that in bind, the first parameter is the john.says function, and the second is the john object. In bindAll, the first parameter is the john object and the second parameter is not a function, but the name of the property.
What to look for
When developing Backbone.js applications, someone wrote the code as follows:
window.ProductView = Backbone.View.extend({
initialize: function() {
_.bind(this.render, this);
this.model.bind('change', this.render);
}
});
The code above will not work because the return value of bind is not used. Proper use will be as follows:
window.ProductView = Backbone.View.extend({
initialize: function() {
this.model.bind('change', _.bind(this.render, this));
}
});
Or you can use bindAll as shown below:
window.ProductView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, this.render);
this.model.bind('change', this.render);
}
});