JavaScript .bind()

Mozilla Developer Network provides a few nice examples of why and how to use .bind(). I’m going to add a few of my own here, but I encourage you to check theirs too.

Let’s say you have a john object with a function that knows how to get its own name:

var john = {
  name: 'John',
  getName: function() { return this.name; }
};

and a mary object that doesn’t:

var mary = {
  name: 'Mary'
};

You can call .getName() on the john object and get the name back:

john.getName(); // 'John'

But you can’t do that to the mary object:

mary.getName(); // TypeError: Object #<Object> has no method 'getName'

You also can’t just call the function because it’s a method of the john object, not a global function:

getName(); // ReferenceError: getName is not defined

Since john and mary are both people, it would make sense for .getName() to be defined on their common prototype, but let’s ignore that solution for now and focus on how to create a function that lets us reuse john.getName to return the name of the mary object.

The simplest way is probably with .call(), which calls the function (in this case, john.getName) with a context of whatever parameter is passed into .call(), in this case, mary:

john.getName.call(mary);  // returns "Mary"

Another way is with .bind(). We create an entirely new function by grabbing the function we want to use (john.getName) and then binding it to the object on which we want the function to be called (mary):

var getMaryName = john.getName.bind(mary);

Now, when we call/invoke the getMaryName function, it will call john.getName and set “this” (in “function() { return this.name; }”) to the mary object, so the correct value gets returned:

getMaryName(); // 'Mary'

This is an entirely new function, not a method of the mary object. So, mary still does not know how to return her own name:

mary.getName();     // TypeError: Object #<Object> has no method 'getName'
mary.getMaryName(); // TypeError: Object #<Object> has no method 'getMaryName'

The function belongs to the global scope and is disembodied from the john and mary objects. And we have cluttered up the global namespace with another variable name and consumed additional memory by creating a new function you may never use again. A better approach is probably to create the new function and immediately call it. Let’s delete the variable and function:

getMaryName = null;

And instead define and execute the new function in a single step:

(john.getName.bind(mary))();  // returns "Mary"

This is a roundabout way of doing what john.getName.call(mary) does. But there will be times when you actually do want to create a new function that is bound to a particular object.

A more verbose, but possibly useful, way to do this is by defining and immediately executing an anonymous function into which you pass the object you wish the function to bind to, in this case, mary:

(function (obj) { return john.getName.bind(obj)() })(mary);  // returns "Mary"

To capture the value, simply assign the value of the function definition-and-invocation to a variable:

var mary_name =  (john.getName.bind(mary))();  // returns "Mary"
console.log(mary_name);

To summarize: func.bind(obj); takes a function (func) and an object (obj) and returns a new function that, when called, calls func with “this” set to obj.

You can also use .bind() to insert additional arguments at the beginning of a method call’s arguments list. Let’s give John the ability to set and get an array of his favorite people in the world and make sure — because everyone loves their mother and would never want to forget their mom — the array always holds at least John’s mom. We’ll create a method that takes the arguments array, adds ‘mom’ at the beginning, and then stores the array in john.favorite_people:

john.set_favorite_people = (function() {
                              this.favorite_people = Array.prototype.slice.call(arguments);
                            }
                           ).bind(john, 'mom');

We’ve defined a method (john.set_favorite_people) that sets john.favorite_people with an array holding ‘mom’ plus all the arguments passed into the set_favorite_people method call.

Now John can stop worrying about forgetting his mom because she’s always automatically listed first in his list of favorite_people:

john.set_favorite_people();
john.favorite_people         // ["mom"]

john.set_favorite_people('dad');
john.favorite_people         // ["mom", "dad"]

Posted by James on Sunday, March 24, 2013