bind, call and apply in JavaScript
Prerequisites
You should have some familiarity with this keyword and some basics like functions, function invocations, objects, methods etc.
Why learn bind, call and apply?
A method often needs to access the object it was called on.
Every function seems to have a this variable for this purpose, which is accessible from within the function's body (I have a separate blog on this topic here).
const personOne = {
firstName: "Tom",
greet: function () {
console.log("hi, my name is " + this.firstName);
},
};
personOne.greet();
Here, the greet method needs to access personOne to use its name property for printing out a greeting.
What if we want to provide a different value of this to a function, without having to rewrite it as the method of a new object? 🤔 There are certain ways to make this happen, let's look at them one by one.
1. bind
I was a little overwhelmed when I first tried to grasp it, let's break it down into smaller parts to make it easier to understand.
bind()is a method that can be called on a function itselfit returns a new function which is a copy of the given function
the returned function's
thiskeyword is set to the value provided as the first argument tobind().
So, basically bind() is used to create a function which has a fixed value of this regardless of the object it is called on. People who have used class-based components in React.js must've used it a lot.
Let's see the same example from the previous section but this time we will add few things to use bind.
const personOne = {
firstName: "Tom",
greet: function () {
console.log("hi, my name is " + this.firstName);
},
};
const personTwo = {
firstName: "Jerry",
};
// extracting the function from our object and keeping it in the global scope
const unboundGreet = personOne.greet;
// using bind on the function from before to get a new function with a fixed value of 'this'
const boundGreet = unboundGreet.bind(personTwo);
personOne.greet(); // hi, my name is Tom
unboundGreet(); // hi, my name is undefined
boundGreet(); // hi, my name is Jerry
Message printed by personOne.greet() could access the firstName property "Tom" because greet was called on personOne object and hence it could be accessed through this binding.
But when we extract the same function to the global scope and call it without personOne object by writing unboundGreet(), the name is printed as undefined in the message. This happens because now the same function is being called on the global object where firstName doesn't exist.
Then we have boundGreet variable which is used for storing the function returned upon calling bind on our unbound function unboundGreet. Since we pass personTwo as an argument to bind, the retuned function's this is also set to personTwo. That's why the name is printed as "Jerry" in the message when we call boundGreet function.
2. call
call can be used to call/invoke a function with a value of our choice set as its this.
We can already see a difference between bind and call here. With bind we could set a function's this to the value of our choice even before invoking it. But with call the value of this in a function is set upon invoking it.
Let's look at the same example from before again but this time with call.
const personOne = {
firstName: "Tom",
greet: function (greeting) {
console.log(greeting + ", " + "my name is " + this.firstName);
},
};
const personTwo = {
firstName: "Jerry",
};
personOne.greet("hello"); // hello, my name is Tom
personOne.greet.call(personTwo, "hi"); // hi, my name is Jerry
personOne.greet could access personOne's name property again, reason we discussed in the previous section. But this time we also passed an argument to personOne.greet and it was printed in the message as expected.
But when we invoke the same function again using call we get to choose the value we want for its this, here we passed personTwo as that value. That's why the function's this is set to personTwo even though we are calling it on personOne and hence the printed message has personTwo's firstName property.
Now, what if we are using call to invoke a function and also want to pass some arguments to that function?
So when we use call(), any arguments following the first one (which will be assigned as the value of this) will be passed to the function as normal arguments. In our example personOne.greet.call()'s first argument personTwo is used as this and "hi" is passed as an argument to the greet function itself.
3. apply
Just like call, apply allows us to invoke a function with a value of our choice set as its this. But the difference is that it takes an array as its second argument and elements of that array are passed as arguments to the function.
Our old example will look like this if we use apply.
const personOne = {
firstName: "Tom",
greet: function (greeting) {
console.log(greeting + ", " + "my name is " + this.firstName);
},
};
const personTwo = {
firstName: "Jerry",
};
personOne.greet("hello"); // hello, my name is Tom
personOne.greet.apply(personTwo, ["hi"]); // hi, my name is Jerry
As we can see it's pretty similar to call. The first argument passed to personOne.greet.apply() changes it's this from personOne to personTwo which is reflected in the message it prints to the console.
The only difference is that apply() takes an array as a second argument whose elements are passed as arguments to the function we are invoking using apply().
That's it for now, thanks for reading.
