August 18, 2011
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.
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.
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.
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.
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.
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);
},
});
If you like this blog then most likely you will also like our videos series on "Understanding this in JavaScript" at Learn JavaScript .
If this blog was helpful, check out our full blog archive.