preface

In “most cases”, this follows the pointing mechanism. In other cases this does not follow this mechanism. To change the direction of this, we have two main ways:

  • Do this by changing the way the code is written (such as arrow functions).
  • Explicitly call some method to help (such as call/apply/bind).

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

The naysayer arrow function

var n = 1

var obj = {
  n: 2.// Declare the location
  sayN: () = > {
      console.log(this.n)
  }
}

// Call location
obj.sayN() / / 1
Copy 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('icon') // Person {name: "icon"}
Copy the code

The this inside the constructor is going to be bound to the object that we’re new out of

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?

It will be much clearer if you combine it with this picture

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

Call, apply, and bind are different in that they change the direction of this and also execute 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

Emulation of the Call method

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

var me = {
  name: 'icon'
}

function showName() {
  console.log(this.name)
}

showName.call(me) // icon
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:
    • Change the direction of this by binding this to the object specified by the first input parameter.
    • 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: 'icon'.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) // icon
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) {
    // 1: attach the function to the target (this is the function we want to modify)
    context.func = this
    // 2: executes the function
    context.func()
    // 3: Delete the function attached to the target object in 1, and return the target object "intact"
    delete context.func
}
Copy the code

Have a try

var me = {
  name: 'icon'
}

function showName() {
  console.log(this.name)
}

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

So far, we’ve implemented changing this to point to 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: 'icon'
}

function showFullName(surName) {
  console.log(`The ${this.name} ${surName}`)
}

showFullName.call(me, 'lee') // icon 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 extension of the ES6 array

/ / '... 'This extension operator helps us turn a list of incoming arguments into an array
function readArr(. args) {
    console.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)
    context.func = this
    context.func()
    delete context.func
}
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, ... args) {
    context.func = this
    // Executes the function to expand the array using the extension operatorcontext.func(... args)delete context.func
}
Copy the code

Now let’s test the fully functional myCall method:

Function.prototype.myCall = function(context, ... args) {
    context.func = thiscontext.func(... args)delete context.func
}

var me = {
  name: 'icon'
}

function showFullName(surName) {
  console.log(`The ${this.name} ${surName}`)
}

showFullName.myCall(me, 'lee') // icon lee
Copy the code

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? Wait — these are small things.

When the interviewer asks you “how do you simulate the implementation of the Call method”, what he really wants to hear is the implementation of these two core function points. The rest is just icing on the icing

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.

Emulation of the Apply method

The implementation of Apply is very similar to call, except in the handling of parameters. I won’t say much here, but go straight to the code (note the comments) :

Function.prototype.myApply = function(context, args){
    // 1: checks whether the current parameter is an array
    if(args && ! (argsinstanceof Array)) {throw new TypeError('Yup yup, the arguments must be arrays.')}// 2: Null points to window by default
    context = context || window
    // 3: attach function to target object (this is the function we want to modify)
    context.func = this
    // 4: Executes the function and stores the return value described above
    const result = context.func(args ? [...args] : ' ')
    // 5: Delete the function attached to the target object in 1, and return the target object "intact"
    delete context.func;
    // 6: Returns the result value
    return result;
}
Copy the code

Emulation of the bind method

Bind’s implementation is a little bit trickier because you need to return a function, and you need to judge some boundary conditions. I won’t say much here, but go straight to the code (note the comments) :

Function.prototype.myBind = function (context, ... args) {
    // 1: Save the current this (this is the function we want to modify)
    const _this = this;
    // 2: returns a function
    return function F() {
      // 3: It returns a function that can also be called new F(), so it needs to be separated
      // 4: New way
      if (_this instanceof F) {
        return new_this(... args, ... arguments); }/ / 5: Since bind can implement code like f.bind(obj, 1)(2), we need to concatenate the two sides of the argument, thus creating the implementation of args.concat(... The arguments);
      return _this.apply(context, args.concat(...arguments));
    }
}
Copy the code