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 precisely 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 parameter is a function john.says
and the second parameter is the value of this john
. In bindAll
first parameter 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);
}
});
Recommended videos
If you like this blog then most likely you will also like the four videos series on “Understanding this in JavaScript” at Learn JavaScript .