Understand call, bind and apply methods in JavaScript
This is frequently asked question in JavaScript interview. This post describes the Function prototype methods call()
, apply()
and bind()
with their syntax, usage and practical examples.
What is Function.prototype?
First of all we need to understand that all these three functions call, apply and bind are prototype of Function
. What does that mean?
Let’s print Function’s structure to understand it.
console.dir(Function);
▼ ƒ Function()
arguments: (...)
caller: (...)
length: 1
name: "Function"
▼ prototype: ƒ ()
arguments: (...)
caller: (...)
length: 0
name: ""
➤ constructor: ƒ Function()
➤ apply: ƒ apply()
➤ bind: ƒ bind()
➤ call: ƒ call()
➤ toString: ƒ toString()
➤ Symbol(Symbol.hasInstance): ƒ [Symbol.hasInstance]()
➤ get arguments: ƒ ()
➤ set arguments: ƒ ()
➤ get caller: ƒ ()
➤ set caller: ƒ ()
You can see apply, bind and call are prototype functions of Function
, that means you can use these three functions on any new function you define.
When to use call(), bind() and apply()
When you define a new function and call it from somewhere in your code, it is executed in a context. This context is called this
and it refers to an object. This object can vary depending upon how you called a function.
var a = 1;
var increment = function(){
return console.log(this.a + 1);
}
var obj1 = { a: 2, increment: increment };
var obj2 = { a: 3, increment: increment };
increment(); //this = window
obj1.increment(); //this = obj1
obj2.increment(); //this = obj2
Output
2
3
4
We can see in above code snippet that we called same increment()
function in three different context which changes the this
reference and resulting into three different output.
Here,
- first
increment()
function is called in global context wherethis
binds to global object.window
is the global object if you are working in browser. - next two
increment()
functions are called in implicit context wherethis
binds to the object on which function is called. This is called implicit context because function is tightly coupled with object (defined inside object) andthis
reference binds to object at compile time when you call a function from object likeobj.ƒn()
Sometime we may want to call a function in explicit context where we can control on this
reference at runtime when calling a function. This is where call(), apply() and bind() comes into play. Let’s call the same increment()
function using call(), apply() and bind() methods.
var increment = function(){
return console.log(this.a + 1);
}
var obj1 = { a: 4 };
var obj2 = { a: 5 };
increment.call(obj1); //this = obj1
increment.call(obj2); //this = obj2
increment.apply(obj1); //this = obj1
increment.apply(obj2); //this = obj2
var bindFn1 = increment.bind(obj1);
var bindFn2 = increment.bind(obj2);
bindFn1(); //this = obj1
bindFn2(); //this = obj2
Output
5
6
5
6
5
6
Few points to note from above code snippet:-
- All these three call(), apply() and bind() are prototype of Function so you are able to use them on any function
increment.call()
,increment.apply()
andincrement.bind()
. - All these three call(), apply() and bind() providing different object context (obj1, obj2) at runtime and resulting into different output.
- Where call() and apply() execute a function immediately, bind() returns a bound function which can be executed later.
Now that we have understood the basic usage of these prototype functions, let’s look at their syntax and practical usage with example.
call()
Syntax
functionName.call(thisArg, arg1, arg2, ...)
When a function is called using call()
,
this
refers tothisArg
- Comma separated arguments
arg1, arg2 ...
are the arguments of function
Remember: “call() arguments are separated by commas”.
Practical Usage
1. Borrow functionality of other objects
In Javascript, every object can have prototype functions which are actually meant to be executed only on that object. Using call() methods, you can borrow the functionality of these objects and execute it on different object.
Let’s look at the Array prototype functions
console.dir(Array);
▼ ƒ Array()
arguments: (...)
caller: (...)
length: 1
name: "Array"
▼ prototype: ƒ ()
length: 0
➤ constructor: ƒ Array()
➤ concat: ƒ concat()
➤ copyWithin: ƒ copyWithin()
➤ fill: ƒ fill()
➤ find: ƒ find()
➤ findIndex: ƒ findIndex()
➤ lastIndexOf: ƒ lastIndexOf()
➤ pop: ƒ pop()
➤ push: ƒ push()
➤ reverse: ƒ reverse()
➤ shift: ƒ shift()
➤ unshift: ƒ unshift()
➤ slice: ƒ slice()
➤ sort: ƒ sort()
➤ splice: ƒ splice()
➤ includes: ƒ includes()
➤ indexOf: ƒ indexOf()
➤ join: ƒ join()
➤ keys: ƒ keys()
➤ entries: ƒ entries()
➤ values: ƒ values()
➤ forEach: ƒ forEach()
➤ filter: ƒ filter()
➤ flat: ƒ flat()
➤ flatMap: ƒ flatMap()
➤ map: ƒ map()
➤ every: ƒ every()
➤ some: ƒ some()
➤ reduce: ƒ reduce()
➤ reduceRight: ƒ reduceRight()
➤ toLocaleString: ƒ toLocaleString()
➤ toString: ƒ toString()
Let’s borrow the functionality of Array prototype methods using call() function:-
Array.prototype.concat.call([1,2,3], [4,5]);
Array.prototype.join.call([1,2,3,4,5], ":")
Output
[1, 2, 3, 4, 5]
1:2:3:4:5
Similarly borrow the start
functionality of car
object to use it for aircraft
var car = {
name: 'car',
start: function() {
console.log('Start the ' + this.name);
},
speedup: function() {
console.log('Speed up the ' + this.name)
},
stop: function() {
console.log('Stop the ' + this.name);
}
};
var aircraft = {
name: 'aircraft',
fly: function(){
console.log('Fly');
}
};
car.start.call(aircraft);
Output
Start the aircraft
2. Chaining constructors
function Box(height, width) {
this.height = height;
this.width = width;
}
function Widget(height, width, color) {
Box.call(this, height, width);
this.color = color;
}
function Dialog(height, width, color, title) {
Widget.call(this, height, width, color);
this.title = title;
}
var dialog = new Dialog('red', 100, 200, 'Title');
We can see in above code snippet how we can chain the constructors by calling parent constructor function in current this
context
apply()
Syntax
functionName.apply(thisArg, [arg1, arg2, ...])
When a function is called using apply() method,
this
refers tothisArg
- second argument is an array of values
[arg1, arg2, ...]
, are the arguments of function
Remember: “apply() accepts arguments as an Array”
Practical Usage
Practical usage of apply() function is same as call() function. The only difference between them, apply() accepts args as an array whereas call() accepts args as comma separated values.
We can pass arguments as an array using apply() function to call any function which accepts args as comma separated values.
Math.min(1, 2, 3, 4, 5); //args as comma separated
Math.min.apply([1, 2, 3, 4, 5]); //args as an array
bind()
Syntax
functionName.bind(thisArg)
When a function is called using bind() method,
this
refers tothisArg
- returns new bound function which can be called later
Remember: “bind() doesn’t call the function immediately. It returns a new bound function which can be called later.
Practical Usage
1. Bounded context
var Button = function(content) {
this.content = content;
};
Button.prototype.click = function() {
console.log(this.content + ' clicked');
};
var myButton = new Button('OK');
myButton.click();
var looseClick = myButton.click;
looseClick(); // not bound, 'this' is not myButton - it is the global object
var boundClick = myButton.click.bind(myButton);
boundClick(); // bound, 'this' is myButton
Output
OK clicked
undefined clicked
OK clicked
2. Binding functions with parameters
var logProp = function(prop) {
console.log(this[prop]);
};
var Obj = {
x : 5,
y : 10
};
Obj.log = logProp.bind(Obj);
Obj.logX = logProp.bind(Obj, 'x'); //binding with prop x
Obj.logY = logProp.bind(Obj, 'y'); //binding with prop y
Obj.log('x');
Obj.logX();
Obj.log('y');
Obj.logY();
Output
5
5
10
10
Another example,
var sum = function(a, b) {
return a + b;
};
var add5 = sum.bind(null, 5);
//add5 is binding function with a = 5
console.log(add5(10)); //b =10
Output
15
Call vs Bind vs Apply
Comparison between function objects, function calls, call, apply and bind:
time of function execution |
time of this binding |
|
---|---|---|
function object ƒ | future | future |
function call ƒ() | now | now |
ƒ.call() | now | now |
ƒ.apply() | now | now |
ƒ.bind() | future | now |
Let’s look at last example of this post and use call(), apply() and bind() methods all together
let numObj1 = {num: 1};
let numObj2 = {num: 2};
let sumFn = function(...args){
console.log(this.num + args.reduce((a,b)=> a+b, 0));
}
sumFn.call(numObj1, 1, 2, 3, 4); //this = numObj1
sumFn.call(numObj2, 1, 2, 3, 4); //this = numObj2
sumFn.apply(numObj1, [1,2,3,4]); //this = numObj1
sumFn.apply(numObj2, [1,2,3,4]); //this = numObj2
let sumBindFn1 = sumFn.bind(numObj1); // return Fn
let sumBindFn2 = sumFn.bind(numObj2); // return Fn
sumBindFn1(1, 2, 3, 4); //this = numObj1
sumBindFn2(1, 2, 3, 4); //this = numObj2
output:
11
12
11
12
11
12