JavaScript call(), apply() and bind() explained like you are five

JavaScript call(), apply() and bind() explained like you are five

Hello World, Akwaaba ๐Ÿ‘‹. JavaScript newbies find it confusing to understand the difference between call(), apply() and bind() methods and how the this keyword fits in their world. In a snapshot, these methods give you the ability to borrow a method of an object without creating a copy of that method.

In this post, I'll help you understand how to use call(), apply(), and bind() to borrow another object's method. Let's get started ๐Ÿƒ

Brief intro to Properties, and Methods

A JavaScript object can contain a function, function that are stored in object properties are called methods. Methods allow object to act or do something.

When the method is being defined in an object, we may want to access some properties or data of the object in the method's body to complete the task. We can also pass arguments to the method.

The code below shows a user object with a greetings method and a parameter passed to the method. When the code is executed, it greets the user by name using the time of the day.

const user = {
    firstName: "Emmanuel",
    lastName: "Kumah",
    greet: function greetings(timeOfDay){
        console.log(`${timeOfDay} ${user.firstName} ${user.lastName}`)
    }
}

In the greetings method's body, because we want to access the user's firstName and lastName, we can do like below :

  • user.firstName which gives access to the value Emmanuel

  • user.lastName which gives access to the value Kumah

A better option is to use the this keyword in place of the user object. So we can rewrite the code above like:

const user = {
    firstName: "Emmanuel",
    lastName: "Kumah",
    greet: function greetings(timeOfDay){
        console.log(`${timeOfDay} ${this.firstName} ${this.lastName}`)
    }
}

What this refers to

  • The this keyword in the function's body always refers to the object it belongs to. It is the object calling the function
  • In the global context this will refer to the window object or undefined when in strict mode.

To execute the greetings function, we call it like below:

 /*Call greet method and pass in the argument "Good morning" */
user.greet("Good morning");

The output will be :

/*Good morning Emmanuel Kumah */
  • The this keyword in the function body will always point to the object calling the function.So in our case, the object calling the function will be user. As a result, wherever the keyword this is seen in the function's body it will point to the user object.

  • In other words, the this.firstName will be evaluated as user.firstName which will give as the value : Emmanuel, likewise , this.lastName will be evaluated as user.lastName which will give as the value: Kumah.

Understanding what the this keyword refers to in a function's body will be pivotal in understanding the call(), apply() and bind() methods.

Understanding the call(), apply() and bind() methods ?

To understand what, when, and how to use the call(), apply() and bind() methods, let's take a look at the code sample below which allows you to book a train in the city of Accra, Ghana's capital

const eurostar = {
    train: "Eurostar",
    ticketCode : "Tr", 
    book(fullName, ticketID,  departure){
        console.log(`Hello ${fullName}, you have booked  ${this.train} train,
        your ticket id is ${this.ticketCode}${ticketID} and departs at ${departure}`)
    }
}
/* Invoke the method and pass the needed arguments */
eurostar.book("Emmanuel Kumah", "ES001", "8:00 am")

The output will be :

/* Hello Emmanuel Kumah, you have booked  Eurostar train, 
your ticket id is TrES001 and departs at 8:00 am */

What is happening in the code above?

  • We defined a train object eurostar with the following properties, train (name of the train station), ticketCode and a book method with fullName, ticketID and departure parameters.

  • What the function does is display the booking details to the user.

  • To execute the function, we then call or invoke it like eurostar.book()

  • We then pass the arguments to the parenthesis

To execute the function body, we invoke or call it and pass the needed data to the function's body like below:

eurostar.book("Emmanuel Kumah", "ES001", "8:00 am")

The output will be :

/* Hello Emmanuel Kumah, you have booked  Eurostar train, 
your ticket id is TrES001 and departs at 8:00 am */

Borrowing another object's method.

What if we decide to use the same book method for another train station. What will happen ?

Because we want to write dry code, we decide to borrow the book method in the eurostar object and execute it on any object that want to make use of it.

So let's access the book method in the eurostar object and store it in the trainBooking variable.

/* copy the book method and assign to variable */
const trainBooking= eurostar.book;

The trainBooking variable is now a copy of the book method. Now trainBooking is also function.

To execute the code in the trainBooking function, let's call or invoke the function.

Remember the function still has parameters so we need to pass arguments to the trainBooking function

Let's execute the function as below:

/* call the function and pass in the arguments */
trainBooking("Stephen Sam", "ES90","6:00pm");

The output will be:

/* Hello Stephen Sam, you have booked  undefined train, your ticket id is undefinedES90 and departs at 6:00 pm */

Why are we getting undefined when the code is executed ๐Ÿ˜ž?

  • When the book method / function is copied to the trainBooking variable, the variable now stores a function which still has the this keyword in the block of code.

To find outconsole.log(trainBooking).

  • In a regular function, the this keyword in the function's body will point to the globalwindow object. However, the window object does not have the train and ticketCode properties.

  • Now because we cannot access the train and ticketCode properties in the window object, executing the this.train and this.ticketCode will have the value undefined. ๐Ÿ˜ฒ

Does that mean we cannot borrow another object's method and execute the code in it ?

We can, let's see how to do that below ๐Ÿ’ช

How do we fix it ?

So how do we fix this, how do we borrow another objects method and use it without encountering any errors. ?

  • To fix it, we need to explicitly tell the function what the this keyword in it should refer to.

There are three function methods to use:

  • The call() method

  • The apply() method

  • The bind() method

Using the JavaScript call() method for function borrowing

The call() allows for a function/method belonging to one object to be assigned and called for a different object.

  • What this means is, using the call() method, it is possible to invoke an object's method on a totally different object

  • With call(), you can write a method once and then inherit it in another object, without having to rewrite the method for the new object

How to use the call() method

The syntax for the call() method is as below

functionName.call(thisArg, arg1, arg2, ...);
  • The call() method calls a function functionName with a given this value and arguments.

  • The first argument of the call() method, thisArg is the value of this. Because the this value always refers to an object, we can simply write the object's name as the first argument

  • Now anytime the code is being executed and we come across, this keyword, it will refer to the object passed as the first argument.

  • arg1, arg2... are the remaining arguments or data you want to pass to the function

Using the call() method .

Now that you have understood, why we need the call() method, let's now borrow the book method defined in the eurostar object and execute it on another train object.

Let's define a new train object fastTrack which also have train and ticketCode properties , then borrow the book method and use it on the fastTrack train object.

The code below defines two objects: eurostar and fastTrack

const eurostar = {
    train: "Eurostar",
    ticketCode : "Tr", 
    book(fullName, ticketID,  departure){
        console.log(`Hello ${fullName}, you have booked  ${this.train} train,
        your ticket id is ${this.ticketCode}${ticketID} and departs at ${departure}`)
    }
}

/* object 2 */
const fastTrack =  {
    train: "fastTrack",
    ticketCode: "Tr"
}

/* copy the book method from eurostar object and assign to the trainBooking variable */

const trainBooking= eurostar.book;

Let's use the call() method to borrow the trainBooking method of the eurostar object on the fastTrack object.

trainBooking.call(fastTrack, "Roberta Takyi", "FS032", "9:40pm")

The output will be :

/* Hello Roberta Takyi, you have booked  fastTrack train, your ticket id is TrFS032 and departs at 9:40 pm */

Technically, the fastTrack object has borrowed the book method of the eurostar object.

Why does the above work ๐Ÿ˜ฒ?

Unlike the previous call, we are not getting any undefined when we use the call() method, because:

  • We explicitly set the first argument of the call() method to the object, fastTrack

  • Now, we pass the rest of the arguments, Roberta Takyi, FS032 and 9:40 pm to the trainBooking function

When an object uses a method of another object is called the function borrowing.

Using the call() method, we can now borrow a method and use it on any object we define.

Another example of the call() method.

Now to conclude on the call() method, let's define a greet function that can greet any person depending on the time of day.

const person1 = {
    firstName: "Emmanuel",
    lastName: "Kumah"
}
const person2 = {
    firstName: "Josephine",
    lastName: "Tetteh"
}
/* Define the greet function */
function greet(timeOfDay){
        console.log(`${timeOfDay} ${this.firstName} ${this.lastName}`)
    }
/*use  greet function on any of the person object using the call method*/ 
greet.call(person1, "Good morning") 
/* Good morning Emmanuel Kumah */
greet.call(person2, "Good morning")
/* Good morning Josephine Tetteh */

With the above

  • We called the greet function on the person1 and person2 objects.
  • The first argument of the call() method is the object the this keyword in the function body will refer to
  • The first argument will now be the person1 and person2 objects. As a result, anywhere we see the this keyword in the function body it will point to the object in the first argument.
  • The rest of the argument, will be any other data we want to pass to the function parameters.

Using the apply() method to borrow another object's method

Let's see how we can now use the apply() method to do the same thing as above.

We take a look at the eurostar train object created above , borrow the book method and instead of using the call method let's try with an apply() method.

What is the apply() or Function.prototype.apply()?

The Function.prototype.apply() method allows you to call a function with a given this value and arguments provided as an array.

  • The apply() method invokes the function and allows you to pass in arguments as an array.

Here is the syntax for the apply() method :

functionName.apply(thisArg, [args])

The apply() method accepts two arguments

  • The thisArg is the object which becomes the value of the this in the function

  • The args argument is an array that specifies the arguments of the function functionName

So if we wanted to use the apply() method on the fastTrack object, the code will be as below

const eurostar = {
    train: "Eurostar",
    ticketCode : "Tr", 
    book(fullName, ticketID,  departure){
        console.log(`Hello ${fullName}, you have booked  ${this.train} train,
        your ticket id is ${this.ticketCode}${ticketID} and departs at ${departure}`)
    }
}
 /* copy the book method and assign to a variable */
const trainBooking= eurostar.book;

const fastTrack =  {
    train: "fastTrack",
    ticketCode: "Tr"
}
trainBooking.apply(fastTrack, ["Roberta Takyi", "FS032", "9:40pm"])

The output will be :

/* Hello Roberta Takyi, you have booked  fastTrack train, your ticket id is TrFS032 and departs at 9:40 pm */
  • The function book accepts three parameters, fullName, ticketID and departure

  • The this value in the function's body will be set to the fastTrack object.

  • The arguments will be passed as an array ,["Roberta Takyi", "FS032", "9:40pm"]

Difference between call() and apply() method.

The only difference of apply() with the call() method is that the second parameter of the apply() method accepts the arguments to the actual function as an array.

  • Instead of passing the argument to the function one by one, with the apply() method, you pass the argument as an array.

Using the bind() method to borrow a method

The bind() method returns a new function, when invoked, has its this sets to a specific value.

The this keyword is set to whatever value we pass to bind().

The following illustrates the syntax of the bind() method

fn.bind(thisArg[, arg1[, arg2[, ...]]])

Let now illustrate how to use the bind() method.

/* Access the function */
const booking = eurostar.book;
/*Bind the booking function to the fastTrack object */
const  bookFastTrack = booking.bind(fastTrack)

bookFastTrack("Ivy Donkor",  "FS909",  "7:00pm")

The output will be :

/* Hello Ivy Donkor, you have booked  fastTrack train, your ticket id is TrFS909 and departs at 7:00 pm */

What is happening in the code above

  • We access the book function in the eurostar object and store it in the booking variable.

  • When we use the bind() method on the booking function, it is not invoked immediately instead, it returns a new function, with the this keyword in the function bound to the fastTrack object

  • We now store the returned function in a bookFastTrack variable

  • Finally, we invoke the bookFastTrack function and we pass it the need argument to get the expected output

So we are binding the booking method to the fastTrack object.

In Conclusion

  • In summary, the call(), apply() and bind() methods are mostly used to borrow a method from another object without making a copy of that method. This is known as function borrowing.

  • You can use the call() or apply() to invoke a function immediately.

  • The call() will basically call a method on an object, with the this value set to the object. It helps you manually set the object

  • The bind() method can be useful when a function needs to be called later in certain events

Did you find my article insightful, please help spread the word by sharing it on your social platforms? I would also love to read comments and feedback from you. Me daa se

PS: Akwaaba and Me daa se are Ghanaian ๐Ÿ‡ฌ๐Ÿ‡ญ dialect meaning, Welcome and Thank you respectively โค๏ธ