You can really tell the True JS Ninjas from the Codeacademicians when it comes to proper use of .call()
, .apply()
, and .bind()
.
All three are methods available on the JS function
prototype and are used to reassign the value of this
within the calling function.
The three functions differ in how they accept arguments, in the case of .call()
and .apply()
, and whether they invoke the function or return a new function, as in the case of .bind()
.
As with all things code, it is much easier to explain in an example.
var homer = {
name: 'Homer',
catchPhrase : 'D\'Oh!',
activity : function(){
console.log('Mmmm ' + this.name + ' likes Duff!');
}
};
var barney = {
name: 'Barney',
catchPhrase : 'Buuuuurp!'
};
var speak = function(greeting1, greeting2){
greeting = greeting1 + greeting2 || '';
console.log(greeting + this.catchPhrase);
};
As you can see, Homer and Barney are gregarious guys. They need to be able to speak! But, oh no! speak()
is not a native function to either of them! this.catchphrase
is doomed to be undefined!
speak() ==> undefined
.call()
.call(context, arg1, arg2...)
The first argument that .call()
accepts will be what is bound to the this
keyword inside the function.
Here, homer
is passed as the first argument to .call()
and so homer
becomes bound as this
within the speak()
function.
speak.call(homer) ==> "D'Oh!"
Subsequent arguments can be passed into .call()
and they will be passed to the calling function.
speak.call(homer, 'Hi, Marge! ', ' Hi, Barney! ') ==> "Hi, Marge! Hi, Barney! D'Oh!"
.apply()
.apply(context, [arg list])
.apply()
works in almost the same way as .call()
. The only difference between the two is the type of arguments they accept. .apply()
will accept a single array or array-like object containing all of the arguments to be passed, whereas .call()
accepts a comma separated list.
speak.apply(barney, ['Hi Moe! ', 'Hey Homer!']); ==> "Hi Moe! Hey Homer! Buuuuurp!"
Notice how the values in the array object passed into .apply()
will be passed in order as the parameters to the calling function.
The real place these function methods shine through is you can call non-native methods bound to different objects without having to set them as properties on those objects.
Homer loves drinking beer.
var homer = {
name: 'Homer',
catchPhrase : 'D\'Oh!',
drinkBeer : function(){
console.log('Mmmm ' + this.name + ' likes Duff!');
}
};
homer.drinkBeer(); ===> "Mmmm Homer likes Duff!"
But Barney loves drinking beer, too! But he doesn't have a .drinkBeer()
method. How can Homer share his favorite activity?
homer.drinkBeer.call(barney); ===> "Mmmm Barney likes Duff!"
The same function is called, homer.drinkBeer()
, but by using .call()
we can change the context to bind this
to barney
instead.
.bind()
.bind(context, arg1, arg2....)
.bind()
has a similar function, but works in a fundamentally different way. .apply()
and .call()
both invoke the calling function when they are called. .bind()
returns a function reference with the this
keyword properly bound.
When Homer and Barney are at Moe's Tavern, they rarely stop at one.
var getDrunk = function(cb){
for(var i = 0; i < 6; i++){
cb();
}
};
.getDrunk()
accepts a callback and runs it six times. Sounds like a good way to .drinkBeer()
!
getDrunk(homer.drinkBeer); ===> "Mmmm Homer likes Duff!"...x6
getDrunk(homer.drinkBeer.call(barney)); ===> undefined...x6
.call()
and .apply()
invoked the function when they are called and don't return anything so they cannot be used when a function reference is required.
.bind()
will return a reference to a new function with thethis
keyword context properly bound. It accepts additional arguments the same way as .call()
.
Huh...
fn.bind(context, arg1, arg2....)
will return the following function.
function(){
return fn.call(context, arg1, arg2....);
};
So Barney can get cranked at Moe's too!
getDrunk(homer.drinkBeer.bind(barney); ===> "Mmmm Barney likes Duff!"...x6
.bind()
acts as a closure, so it will create a new function and return a reference to it. If you need to use that bound function multiple times, you can save it as a variable with all the properly bound this
goodness you could want.
var drinkBeerBarney = homer.drinkBeer.bind(barney);
typeof drinkBeerBarney ===> 'function'
getDrunk(drinkBeerBarney); ===> "Mmmm Barney likes Duff!"...x6
So hopefully that will move you on your way toward some JS function binding context-fu mastery. At least it should give you a hankering for a beer, so my job is done.