Thorough Explanation of Apply, Bind and Call in JavaScript
function.prototype.apply
, function.prototype.bind
and function.prototype.call
are all very similar but there are some important distinctions. You should be familiar with all 3 as to avoid writing extra code.
function.prototype.apply
and function.prototype.call
are especially similar. What they do is allow you to overwrite the default scope or context (this
) and supply parameters when calling a function.
They produce the same result albeit with different input parameters. apply
takes 2 parameters, the argument for the value of this
and an array or array-like structure as the arguments to apply. call
takes many optional parameters, the first is for the value of this
and the next n
parameters will be passed to the function. Here are the MDN pages for these functions: apply and call.
You may be wondering, "Why don't I just call the function with whatever parameters I want and skip apply/call?". That's a good point, but that's where applying your own context, or this
, comes in handy.
A very obvious example for call
is to get all of the arguments in a function like so:
function testCallAndApply(){
return Array.prototype.slice.call(arguments);
//return Array.prototype.slice.apply(arguments); //Same output
}
testCallAndApply(1,2,3,4,5) => [1,2,3,4,5]
In the simple example above the function can take any number of arguments and will return them as an array, call
and apply
produce the same result. That's because Array.prototype.slice
typically operates on the instance (e.g, [1,2,3].slice
), which we set by passing arguments
, an array-like object, to use as this
. You could use this in your everyday programming to take an unknown number of parameters then apply some operation on them. The same type of procedure is required when using ES6's rest parameters. For example:
function getSumOfN(firstNumber, secondNumber, ...arguments){
const returnNumber = firstNumber + secondNumber;
const args = Array.prototype.slice.call(arguments);
return returnNumber + args.reduce((number, accumulator) => accumulator += number);
}
getSumOfN(1,2,3,4,5) => 10
Here we take an undetermined amount of numbers and receive the sum by retrieving the additional arguments as an array. A more transparent example might look like the following:
const man = {
name: "Carl",
age: 43,
getAge: function(){ return this.age }
}
const dog = {
name: "Fido",
age: 4
}
man.getAge.bind(dog); => 4
Here man.getAge returns 4 because we're binding the dog object as it's this
argument.
Another use case is fixing the scope of a callback function. Let's add some methods to the man object and see how it's done:
const man = {
name: "Carl",
age: 43,
getAge: function(){ return this.age },
getName: function(){ return this.name },
increaseAge: function(){
this.age++;
return Promise.resolve(this.age);
},
haveBirthday: function(){
const $this = this;
this.increaseAge()
.then(function(age){
//this.getName === undefined;
let name = man.getName.call($this);
console.log(`Happy birthday ${name} must be nice to be ${age}`);
});
}
}
man.haveBirthday(); //logs "Happy birthday Carl must be nice to be 44"
Here we use call
to bind the value of this
on man.getName inside of a callback. This is important because creating a new function creates a new scope with a this
argument that's different from the outer function.
bind
is great for partial application and fixing scope. MDN article for bind. It works a lot like call
except it doesn't actually call the function. It just sets the this
object and whatever parameters you choose. For example:
function printN(n){ console.log(n); }
var x = printN.bind(this, 123);
...
x(); //logs 123
The above code instantiates a function, binds the parameter 123, then calls it at a later time.
Hopefully you can see how bind
lends itself to partial application. If not, here's an example:
const euroToUSD = .08; //Let's say the dollar is trading at $0.80/€1.00
function convertUSDtoEuro(euro, btcInUsd){
return euroToUSD * btcInUsd;
}
let usdToEuro = convertUSDtoEuro.bind(this, euroToUSD);
function reportBTCprice(){
fetch('www.coinbase.com/api/btc') //Not the real API URL
.then(data => data.json())
.then(json => {usd: json.usd, eur: usdToEuro(json.usd)});
}
reportBTCprice.then(price => console.log(`The price is $${price.usd}, €${price.eur}`)); //logs "The price is $[usd], €[eur]"
Above we define a the exchange rate, euroToUSD
, then a function to compute a price in the exchange rate, convertUSDToEuro
. We then do a partial application of convertUSDToEuro
which supplies the exchange rate, storing the resulting function in a variable named usdToEuro
. After we get the bitcoin price we translate it to Euros.
Hopefully this article has provided you with a better understanding of apply
, bind
, and call
. I definitely recommend reading through the mdn pages linked to above.
Feel free to reply with any comments or questions.