Thorough Explanation of Apply, Bind and Call in JavaScript

in #programming7 years ago

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.