Understanding call, apply and bind methods in JavaScript

Understanding call, apply and bind methods in JavaScript

Introduction

In JavaScript, almost everything is an object. An object can have methods - a function that is a property of the object.

const user = {
  firstName: "Alicia",
  lastName: "Keys",
  greet: function () {
    alert("Hello!");
  }
};

user.greet();

In the above example, greet is a method as it is a function that is a property of the object user.

We often come across situations where an object method needs to access another property value within the object.

Let's see an example. Suppose in the printFullName method below, we want to print the user's full name and need to access firstName and lastName. In JavaScript, every function has access to a this keyword and using this, we can access the required property values:

const user = {
  firstName: "Alicia",
  lastName: "Keys",
  printFullName: function() {
    console.log(this.firstName + " " + this.lastName);
   }
};

user.printFullName()

//Output 
//Alicia Keys

In this case, the value of this refers to the object before the dot - "user".

In other object-oriented programming languages, the this keyword always refers to the current instance of the class. However, in JavaScript, the this keyword refers to different objects depending on how it is used:

  • In an object method, this refers to the object.
  • Alone, this refers to the global object.
  • In a function, this refers to the global object.
  • In a function, in strict mode, this is undefined.
  • Methods like call(), apply(), and bind() can refer this to any object.

call(), apply(), and bind()

Suppose in addition to the user mentioned in the above example, we have another user whose full name we wish to retrieve. Shown below is one approach:


const user = {
 firstName: "Alicia",
  lastName: "Keys",
  printFullName: function() {
    console.log(this.firstName + " " + this.lastName);
   }
};

const userTwo = {
 firstName: "Jessica",
  lastName: "Parker",
  printFullName: function() {
    console.log(this.firstName + " " + this.lastName);
   }
};

user.printFullName(); //Alicia Keys
userTwo.printFullName(); //Jessica Parker

Obviously, this is not an efficient way of writing code because it's repetitive. Luckily, we have Function Borrowing. Function borrowing allows us to use the methods of one object on a different object without having to make a copy of that method and maintain it in two separate places. It is accomplished through the use of call(), apply(), or bind(), all of which exist to explicitly set this on the method we are borrowing:

call()

const user= {
 firstName: "Alicia",
  lastName: "Keys",
  printFullName: function() {
    console.log(this.firstName + " " + this.lastName);
   }
};

const userTwo= {
 firstName: "Jessica",
  lastName: "Parker",
};

user.printFullName.call(userTwo); 

//Output
//Jessica Parker

What we tell the code to do here is to use the "printFullName" method of the "user" object on "userTwo".

Similarly, we can also declare the function outside the objects and call it as needed.

const printFullName = function () {
  console.log(this.firstName + " " + this.lastName);
};

const user = {
  firstName: "Alicia",
  lastName: "Keys"
};

const userTwo = {
  firstName: "Jessica",
  lastName: "Parker"
};

printFullName.call(user); //Alicia Keys
printFullName.call(userTwo); //Jessica Parker

call() with arguments

call() can also accept arguments. In the code below, we pass the city "Paris" and country "France" as arguments to the call() method.

const printUserDetails = function (city, country) {
  console.log(
    this.firstName + " " + this.lastName + "," + city + "," + country
  );
};

const user = {
  firstName: "Alicia",
  lastName: "Keys"
};

printUserDetails.call(user, "Paris", "France"); 

//Output
//Alicia Keys

apply()

While the call() method accepts arguments separately, the apply() method accepts arguments as an array. The above example can be rewritten as below using apply(). Notice that we are sending the arguments city and country in an array:

const printUserDetails = function (city, country) {
  console.log(
    this.firstName + " " + this.lastName + "," + city + "," + country
  );
};

const user = {
  firstName: "Alicia",
  lastName: "Keys"
};

printUserDetails.apply(user, ["Paris", "France"]);

//Output
//Alicia Keys,Paris,France

bind()

While call() and apply() invoke the function immediately, bind() returns a copy of the function and sets this to its first argument. The new function is then invoked at a later point in time.

const printUserDetails = function (city, country) {
  console.log(
    this.firstName + " " + this.lastName + "," + city + "," + country
  );
};

const user = {
  firstName: "Alicia",
  lastName: "Keys"
};

const printLater = printUserDetails.bind(user, "Paris", "France");

//the new function printLater is invoked later
printLater(); 

//Output
//Alicia Keys,Paris,France

Conclusion

To wrap it up, we have learned that the this keyword refers to different objects depending on how it is used. We make use of call(), apply(), or bind(), all of which exist to explicitly set this independent of how a function is called.

The call() and apply() methods set the this keyword and invoke the function immediately, whereas the bind() method creates a copy of the function and sets the this keyword, which can be invoked later.

Do let me know your thoughts on this post and share it with your fellow devs if you found it helpful! :)