I noticed a lot of developers were confused about the difference between these 3 built-in javascript functions ( apply, call, and bind ). This is my attempt to explain the difference between them.
Let’s consider this simple function addIntegers that accepts 2 paramaters (x and y):
function addIntegers(x, y){
var total = x - y + this.value;
return total;
}
Notice inside the function that it’s using a this keyword. ( If you’re not familiar with the javascript’s this keyword, please read about it here first ).
If we run the addIntegers function:
let sum = addIntegers(2,3);
console.log(sum); //output is NaN
The output is NaN since by default the this keyword is equal or pointing to the window global object.
And since the window object has no defined value property, this.value is equal to undefined resulting to a sum of NaN.
//actual operation that happened
2 + 3 - undefined = NaN
Now let’s create a custom object that will define a value property to use on our addIntegers function.
function addIntegers(x, y){
let total = x - y + this.value;
return total;
}
let customObject = new Object(); //create an empty object
customObject.value = 20; //define a "value" property that is equal to 20
Calling the addIntegers function:
let sum = addIntegers(2,3);
console.log(sum); //output is still NaN
Remember, our addIntegers this keyword is still pointing to the global window so the output is still NaN
So, how do we call our AddIntegers function so that the inner “this” keyword will refer to the customObject we created?
Apply, call, and bind functions to the rescue. Let’s use them.
Using Apply:
let sum = addIntegers.apply(customObject,[2,3]);
console.log(sum); //output is 19
Using Call:
let sum = addIntegers.call(customObject,2,3);
console.log(sum); //output is 19
Using Bind:
let sum = addIntegers.bind(customObject,2,3)();
console.log(sum); //output is 19
To summarize based from the code above:
apply - returns the function’s output with the second parameter as arrays.
Ex. Function.apply(Object, [param1,param2…])
call - returns the function’s output with the second parameter as normal parameter ( separated by comma if in multiple entries ).
Ex. Function.call(Object,param1,param2….)
bind - returns a bounded function with the second parameter as normal parameter ( separated by comma if in multiple entries ).
Ex. Function.bind(Object,param1,param2….)
It is important to take note that bind returns a bounded function and not a value. Also call and apply differs from the way they accept their parameters.
Here’s an example where apply, call and bind can fix an unexpected closure behavior inside for loop.
function makeArmy() {
var shooters = [];
for(var i=1; i<11; i++) {
var shooter = function(i) {
console.log(i);
};
shooters.push(shooter);
}
return shooters;
}
Executing the makeArmy function:
var army = makeArmy();
var first_shooter = army[0]; // first shooter
first_shooter(); // expected to be 1 but logs 11
var fifth_shooter = army[4] // fifth shooter
fifth_shooter() //expected to be 5 but logs 11
I bet you encountered this unexpected behavior before. The reason is because at the time we called the first_shooter or fifth_shooter function, for loop already finished the looping process.
And since a closure remembers or reference the last value of i from its execution context, obviously it will keep using i = 10.
There are a lot of ways to fix this but let’s see how we can use apply, call and bind to solve the problem elegantly.
First let’s use bind:
function makeArmy() {
var shooters = []
for(var i=1; i<11; i++) {
var shooter = function(param) {
console.log(param)
}.bind(null, i);
shooters.push(shooter)
}
return shooters
}
As you see, we made i as the second parameter of bind function. This will be automatically mapped to the parameter ( param ) of the shooter function. Also
we used null as the first parameter of bind since we don’t care or we don’t use the this keyword reference anyway.
Executing the function will result to the expected behavior:
var first_shooter = army[0]; // first shooter
first_shooter(); // logs 1
var fifth_shooter = army[4] // fifth shooter
fifth_shooter() // logs 5
Using call, we’ll update the makeArmy function like this:
function makeArmy() {
var shooters = []
for(var i=1; i<11; i++) {
var shooter = function(param) {
return function(){
console.log(param)
}
}.call(null, i);
shooters.push(shooter)
}
return shooters
}
Using apply:
function makeArmy() {
var shooters = []
for(var i=1; i<11; i++) {
var shooter = function(param) {
return function(){
console.log(param)
}
}.apply(null, [i]);
shooters.push(shooter)
}
return shooters
}
Both updates using call and apply will yield expected results:
var first_shooter = army[0]; // first shooter
first_shooter(); // logs 1
var fifth_shooter = army[4] // fifth shooter
fifth_shooter() // logs 5