Obstacles are inevitable to genius, for obstacles create genius. — Romain Rolland

In the above article, the basic pointing principle of this is analyzed. What we have talked about over and over again is the pointing mechanism that this follows “in most cases”. In other cases this does not follow this mechanism. To change the direction of this, we have two main ways:

  1. Do this by changing the way you write code (such as the arrow function mentioned in the previous section).
  2. Explicitly call some methods to help.

Both paths are propositional hotspots. The first one, because it’s easier, we’ll start with:

Change the way you write code, and therefore change the direction of this

The naysayer arrow function

The arrow function was discussed earlier. In this section, I would like to emphasize that:

Var a = 1 var obj = {a: 2, showA: () => {console.log(this.a)}} // call obj () // 1Copy the code

When we rewrite a normal function as an arrow function, the arrow function’s this is bound to its parent scope’s this at the writing stage (the declaration position). No matter how we call it later, we can no longer specify a target object for it — because the arrow function’s this pointer is static, “once is a lifetime.”

Constructor this

When we use the constructor to new an instance:

function Person(name) {
  this.name = name
  console.log(this)
}
var person = new Person('xiuyan')
Copy the code

The this in the constructor will bind to our new object:

Explicitly call some methods to help

To change this, we use the call, apply, and bind methods.

Considering the fact that we changed this reference to a lot of scenes in the actual development, the use of these three methods is often examined in the interview. The most common test is to ask about the use and differences between the three methods. Many times, however, in order to further test the depth of your understanding of this concepts, the interviewer will examine the implementation of call, apply, and bind, and may even ask you to write code by hand.

Therefore, we should not only know how to use call, apply, and bind, but also know how they work. Next, we will gather these three methods of investigation into two questions, if you can master these two questions, you can do one thing, know a hundred.

What do call, apply and bind do? How to use it? What are the differences between them?

Here I draw a mind map for you:

It’s much clearer when illustrated with this picture:

Call, apply, and bind are all used to change the this reference of a function.

The difference between call, apply, and bind is that the former changes the “this” direction and also executes the target function. The latter does nothing but transform this.

The difference between call and apply is reflected in the requirements for input parameters. The former simply passes in the object function’s incoming arguments one by one, while the latter expects the incoming arguments to be passed in as an array.

Advanced coding problem: simulate implementing a call/apply/bind method

These three methods are very similar in implementation level. Let’s take the call method implementation as an example to give you an in-depth understanding of the simulation thinking of this kind of method:

Emulation of the Call method

Before implementing the Call method, let’s look at an example of a call:

var me = {
  name: 'xiuyan'
}
function showName() {
  console.log(this.name)
}
showName.call(me) // xiuyan
Copy the code

Given the characteristics of Call, we can think of at least two things:

  • Call is inherited by all functions, so the call method should be defined on function.prototype
  • The call method does two things:
  1. Change the direction of this by binding this to the object specified by the first input parameter.
  2. Executes the function based on the input parameters.

Combining these two, we implement the Call method step by step. First, change the direction of this:

ShowName, when called by the call method, behaves like a method on the me object.

So one of our immediate associations is that it would be nice if we could stuff showName directly into the ME object, like this:

var me = {
  name: 'xiuyan',
  showName: function() {
    console.log(this.name)
  }
}
me.showName()
Copy the code

There is a problem with this, however, because in the call method, me is an input parameter:

showName.call(me) // xiuyan
Copy the code

When the user passes in the me object, all the user wants to do is ask the call to change the “this” in the showName object, rather than add a new showName method to the me object. So after executing me. ShowName, remember to delete it. With this in mind, let’s simulate the call method (note the comment) :

Function.prototype.myCall = function(context) { // step1: Context.func = this // step2: execute context.func() // step3: Delete context.func} delete context.func}Copy the code

If you are interested, you can test our myCall:

showName.myCall(me) // xiuyan
Copy the code

In our example, the myCall result is the same as the call result

At this point, we have implemented the “change the direction of this” function point. Now myCall also needs to be able to read function input parameters, analogous to the call form of call:

var me = {
  name: 'Chris'
}
function showFullName(surName) {
  console.log(`${this.name} ${surName}`)
}
showFullName.call(me, 'Lee') // Chris Lee
Copy the code

It reads the function input parameter, specifically the second to last input parameter of the call method. To do this, we can use the array extender (note the comments, if the ‘… ES6 extension operator: ES6 extension operator: ES6 extension operator:

/ / '... Function readArr(...) function readArr(... The args) {the console. The log (args)} readArr (1, 2, 3) / / [1, 2, 3]Copy the code

We apply this logic to our myCall method:

Function.prototype.myCall = function(context, ... args) { ... Console. log(' Entry is ', args)}Copy the code

We can get the input parameter we want through the args array. Reexpand the target input represented by the args array into the target method, and you’re done:

Function.prototype.myCall = function(context, ... Context. func = this // step2: execute the function and expand the array using the extension operator context.func(... Args) // Step3: Delete the function attached to the target object in step1, and return the target object "complete" delete context.func}Copy the code

Now let’s test the fully functional myCall method:

Function.prototype.myCall = function(context, ... Context. func = this // step2: execute the function and expand the array using the extension operator context.func(... Func} var me = {name:}} var me = {name:}} var me = {name:} 'Chris' } function showFullName(surName) { console.log(`${this.name} ${surName}`) } showFullName.myCall(me, 'Lee') // Chris LeeCopy the code

The result is the same as the call method!

Above, we have successfully simulated a call method.

Based on this basic call idea, you can also add capabilities to this method:

For example, what if we pass null for the first argument? Can I default it to Window? What if the function returns a value? Would you like to create a new result variable to store the value and then return it? Blah, blah, blah — these are small things. When the interviewer asks you “how do you simulate the implementation of the Call method?”, he wants to hear about the implementation of these two core functions

Based on your understanding of the call method, it is not difficult to write a apply method (which changes the form of read arguments) and a bind method (which delays the timing of target function execution). You just need to modify the code above. (That is, if you know the apply and bind methods and how to use them.)