📜 ⬆️ ⬇️

Understanding bind and bindAll in Backbone.js

Backbone.js users often use the bind and bindAll methods provided by the Underscore.js library. In this blog, I'm going to discuss why these methods are needed and how they work.

It all starts with apply


The bindAll function uses bind internally. And bind in turn uses apply. Therefore, it is important to understand what the apply does.

var func = function beautiful(){ alert(this + ' is beautiful'); }; func(); 

If I execute the above written code, I will get "[object window] is beautiful". I get this message because when the function is called, this is equal to the window, the global object by default.

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 the function is called.

Why bind is needed


In order to understand why bind 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 straightforward. The john object is an instance of Developer, and when the says function is called, we get the right alert message.

Notice that when we call says, we call it as follows john.says (). If we just want to master the function that says says, then we need to run john.says. 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, we should receive a message that we are expecting. However, if we run this code, the alert 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 named skill. Thus, the output value of this.skill is undefined.

Earlier we saw that using apply, we can solve the problem due to this. So let's try to use apply to solve.

 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 alert message we received was “Ruby rocks!”. However, there is a problem and rather big.

In the JavaScript world, functions are first class objects. The reason we create a function is that we can easily pass it everywhere. In the above case, we created a function called func. However, along with the func function, we now have to 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 function of the creator to the function of the consumer. This is not a good API.

We should try to create functions that can be easily called by consumers. And here comes the bind.

How bind solves a problem


First, let's see how using bind solves a 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 this problem, we need a function that is already associated with john in such a way that we would 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 yourself, bind uses apply to set this to the second parameter that we passed when we called bind.

Note that bind does not change the 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 the 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 the solution with bind, but there are a few big differences. The first difference is that we don’t need to worry about the bindAll return value. In the case of bind, we have to use the return function. In bindAll, we don’t worry about the return value, but you have to pay your price for it. Actually bindAll modifies the function. What does it mean?

You see that the john object has a property named says, which 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); } 

Notice that bindAll inside itself calls the bind method and it replaces the value of an existing property with the function that bind returned.

Another difference between bind and bindAll is that in bind the first parameter is the function john.says, and the second is the object john. 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 above code will not work, because the bind return value 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); } }); 

Source: https://habr.com/ru/post/231411/


All Articles