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