We write about Ruby on Rails, React.js, React Native, remote work, open source, engineering and design.
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.
1var func = function beautiful() {
2 alert(this + " is beautiful");
3};
4func();
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.
1var func = function beautiful() {
2 alert(this + " is beautiful");
3};
4func.apply("Internet");
In the above case the alert message will be Internet is beautiful
. Similarly following code will produce Beach is beautiful
.
1var func = function beautiful() {
2 alert(this + " is beautiful");
3};
4func.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.
1function Developer(skill) {
2 this.skill = skill;
3 this.says = function () {
4 alert(this.skill + " rocks!");
5 };
6}
7var john = new Developer("Ruby");
8john.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.
1function Developer(skill) {
2 this.skill = skill;
3 this.says = function () {
4 alert(this.skill + " rocks!");
5 };
6}
7var john = new Developer("Ruby");
8var func = john.says;
9func(); // 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.
1function Developer(skill) {
2 this.skill = skill;
3 this.says = function () {
4 alert(this.skill + " rocks!");
5 };
6}
7var john = new Developer("Ruby");
8var func = john.says;
9func.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.
1function Developer(skill) {
2 this.skill = skill;
3 this.says = function () {
4 alert(this.skill + " rocks!");
5 };
6}
7var john = new Developer("Ruby");
8var func = _.bind(john.says, john);
9func(); // 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
1return function () {
2 return func.apply(obj, args.concat(slice.call(arguments)));
3};
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
.
1function Developer(skill) {
2 this.skill = skill;
3 this.says = function () {
4 alert(this.skill + " rocks!");
5 };
6}
7var john = new Developer("Ruby");
8_.bindAll(john, "says");
9var func = john.says;
10func(); //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.
1function(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
1window.ProductView = Backbone.View.extend({
2 initialize: function () {
3 _.bind(this.render, this);
4 this.model.bind("change", this.render);
5 },
6});
Above code will not work because the returned value of bind
is not being used. The correct usage will be
1window.ProductView = Backbone.View.extend({
2 initialize: function () {
3 this.model.bind("change", _.bind(this.render, this));
4 },
5});
Or you can use bindAll
as given below.
1window.ProductView = Backbone.View.extend({
2 initialize: function () {
3 _.bindAll(this, this.render);
4 this.model.bind("change", this.render);
5 },
6});
If you like this blog then most likely you will also like our videos series on "Understanding this in JavaScript" at Learn JavaScript .