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.