Understanding call, apply and bind in JS from a Line of Equations

  Front end, javascript

Regarding call, apply and bind in JS, I believe everyone, like me, has read numerous related articles and all have their own understanding. Therefore, this article is not a popular science article, but just a record of my own understanding.

My study habit is to connect various seemingly isolated knowledge points in series, understand and apply them comprehensively, and understand them thoroughly through the simplest and most intuitive thinking. Therefore, this article will connect a relatively difficult knowledge point in JS, call, apply and bind, through a very simple equation:

cat.call(dog, a, b) = cat.apply(dog, [a, b]) = (cat.bind(dog, a, b))() = dog.cat(a, b)

In order to understand these three keywords in JS, we must first understand what they are used for. To be more complicated, you can refer to the original MDN document:

You can have the object in call () call the function owned by the current object. You can use call () to implement inheritance: write a method and let another new object inherit it (instead of writing the method again in the new object).

To put it simply, one sentence that everyone has read can be quoted:

In order to dynamically change the runtime context of a function.

Or is it

In order to change the direction of this inside the function body

The above explanations are all correct, saying that there is no problem at all, but they have been introduced again.Inheritance,context,thisThese additional knowledge points. If I just want to use the most intuitive way to understand the role of these three keywords, perhaps I can understand it this way:

Define a cat object:

class Cat {
 constructor (name) {
 this.name = name
 }
 
 catchMouse(name1, name2) {
 console.log(`${this.name} caught 2 mouse!  They call ${name1} and ${name2}.`)
 }
 }

This cat object has a skill of catching mice.catchMouse().

Then similarly, define a dog object:

class Dog {
 constructor (name) {
 this.name = name
 }
 
 biteCriminals(name1, name2) {
 console.log(`${this.name} bite 2 criminals!  Their name is ${name1} and ${name2}.`)
 }
 }

This dog can bite bad peoplebiteCriminal().

Next, we instantiate two objects and get a cat named “Kitty” and a dog named “Doggy” respectively:

const kitty = new Cat('Kitty')
 const doggy = new Dog('Doggy')

First, let them play their skills with each other:

kitty.catchMouse('Mickey', 'Minnie')
 // Kitty caught mouse!  They call Mickey and Minnie.
 
 doggy.biteCriminal('Tom', 'Jerry')
 // Doggy bite a criminal!  Their name is Tom and Jerry.

Now, we want to give Doggy the ability to catch mice. If we don’t use these three keywords, what should we do?

Scheme A: Modify the Dog object and directly define a mouse catching skill that is the same as Cat.

Scheme B: Let Doggy eat Kitty and directly digest and absorb all Kitty’s abilities.

In fact, the solutions of Scheme A and Scheme B are similar, and the Dog object needs to be modified, but Scheme B will be simpler and rougher:

class Dog {
 constructor (name, kitty) {
 this.name = name
 this.catchMouse = kitty.catchMouse
 }
 
 biteCriminals(name1, name2) {
 console.log(`${this.name} bite 2 criminals!  Their name is ${name1} and ${name2}.`)
 }
 }
 
 const kitty = new Cat('Kitty')
 const doggy = new Dog('Doggy', kitty)
 
 doggy.catchMouse('Mickey', 'Minnie')
 // Doggy caught 2 mouse!  They call Mickey and Minnie.

The above method is really not elegant, often when defining the Dog object, there is no plan to add a mouse catching method to it. So is there a way to let the Doggy instance have a way to catch mice without modifying the contents of the Dog object? The answer is to use the call, apply, or bind keywords:

kitty.catchMouse.call(doggy, 'Mickey', 'Minnie')
 
 kitty.catchMouse.apply(doggy, ['Mickey', 'Minnie'])
 
 const doggyCatchMouse = kitty.catchMouse.bind(doggy, 'Mickey', 'Minnie')
 doggyCatchMouse()
 
 // Doggy caught 2 mouse!  They call Mickey and Minnie.
 // Doggy caught 2 mouse!  They call Mickey and Minnie.
 // Doggy caught 2 mouse!  They call Mickey and Minnie.

On the other hand, Kitty’s ability to bite bad people can also be achieved through this method, and readers can try it on their own.

Seeing here, I believe the readers can already understand the differences and functions of call, apply and bind, and then look at their respective concepts, which should also be easier to understand.

Returning to the equation at the beginning of the article:

cat.call(dog, a, b) = cat.apply(dog, [a, b]) = (cat.bind(dog, a, b))() = dog.cat(a, b)

The “equal sign” here is actually not rigorous, because the difference between the three keywords and the principle behind it cannot be generalized by just one equal sign, but this equation is not necessarily a good idea for the understanding of the concept and the application in actual situations.