takeaway

As the interviewer’s favorite question in the interview, here is a detailed introduction, we should focus on understanding, rather than reciting. Write bad or wrong place, please actively point out, well, without saying more, we “compass is turning”

Call () and apply() are executed immediately, while call() and apply() are executed immediatelybind2. Call can pass multiple arguments. The first argument, like apply, is the object to replace, followed by a list of arguments. 3. Apply can take a maximum of two arguments -- a new this object and an array argArrayCopy the code

One, handwritten implementation of Call

1. What does call mainly do?

  • Change this to point to
  • The function executes immediately

2. Simple implementation

Function.prototype.myCall = function(context) {
  context.fn = this;
  context.fn();
}

const obj = {
  value: 'hdove'
}

function fn() {
  console.log(this.value);
}

fn.myCall(obj); // hdove
Copy the code

3. Problems arise

  • Can’t pass values
  • If fn() returns a value, myCall will not get it
function fn() {
  return this.value;
}

console.log(fn.myCall(obj)); // undefined

Copy the code
  • Call is simply changing this to point to an Object. What if the user passes a primitive type or doesn’t pass it at all?
  • After myCall is executed, obj will always be tied to fn()

4. Fix it all


Function.prototype.myCall = function(context) {
  // 1. Check if any Object is passed to bind, no default is window, if it is a basic type, use Object() method to convert (solve problem 3)
  var context = Object(context) || window;
  
  /** Create a new fn property on the object obj with the value of this, which means that fn() becomes {value: 'hdove', fn: function fn() {console.log(this.value); /** Create a new fn property on the object obj with the value of this. }} * /
  context.fn = this;
  
  // 2. Save the return value
  let result = ' ';
  
  The first argument is this. Here are three ways to truncate the rest of the argument (solve problem 1)
  const args = [...arguments].slice(1);
  //const args = Array.prototype.slice.call(arguments, 1);
  //const args = Array.from(arguments).slice(1);
  
  // 4. Execute this method and pass in the argument... Es6 syntax for expanding arraysresult = context.fn(... args);//5. Delete the attribute (solve problem 4)
  delete context.fn;
  
  //6. Return (solve problem 2)
  return result;
}


const obj = {
  value: 'hdove'
}

function fn(name, age) {
  return  {
      value: this.value,
      name,
      age
  }
}

fn.myCall(obj, 'LJ'.25); // {value: "hdove", name: "LJ", age: 25}

Copy the code


Implement Apply manually

Implementing call also indirectly implements apply, but with different parameters


Function.prototype.myApply = function(context, args) {
  var context = Object(context) || window;
  
  context.fn = this;
  
  let result = ' ';
  
  //4. Check whether args is passed in
  if(! args) { result = context.fn(); }else{ result = context.fn(... args); }delete context.fn;
  
  return result;
}


const obj = {
  value: 'hdove'
}

function fn(name, age) {
  return  {
      value: this.value,
      name,
      age
  }
}

fn.myApply(obj, ['LJ'.25]); // {value: "hdove", name: "LJ", age: 25}

Copy the code


Implement Bind


bindThe () method creates a new function inbindWhen () is called, the new function's this is specified asbindThe first argument to (), and the remaining arguments will be used as arguments to the new function when called (MDN)Copy the code

1. The bind properties

  • To specify this
  • Return a function
  • Pass the parameters and corrify them

2. Simple implementation


Function.prototype.myBind = function(context) {
    const self = this;
    
    return function() { self.apply(context); }}const obj = {
  value: 'hdove'
}

function fn() {
    console.log(this.value);
}

var bindFn = fn.myBind(obj);

bindFn(); // 'hdove;


Copy the code

3. The optimization

Compared with Call and apply, I personally think the implementation logic of bind is more complicated and there are many things to consider, so I will optimize it separately here.

3.1 What is calling bind?

Here we need to make a judgment call to determine whether bind is a function or not, and throw an error if it is not.


Function.prototype.myBind = function(context) {

    if(typeof this ! = ="function") {
        throw new Error("Not a function.");
    }
    const self = this;
    
    return function() { self.apply(context); }}Copy the code

3.2 Transferring Parameters

Let’s look at the code below


Function.prototype.myBind = function(context) {

    if (typeof this! = ="function") {
        throw new Error("Not a function.");
    }
    const self = this;
    
    return function() { self.apply(context); }}const obj = {
  value: 'hdove'
}

function fn(name, age) {
    console.log(this.value);
    console.log(name);
    console.log(age);
}

var bindFn = fn.myBind(obj, LJ, 25);

bindFn(); // 'hdove' undefined undefined


Copy the code

Obviously, the first optimization is passing parameters, so let’s change that


Function.prototype.myBind = function(context) {

    if(typeof this ! = ="function") {
        throw new Error("Not a function."); } const self = this; // The first argument is this, intercepting const args = [...arguments].slice(1);return function() {/** we can also use call to change the direction of this by using applyreturn self.apply(context, args);
    }
}

const obj = {
  value: 'hdove'
}

function fn(name, age) {
    console.log(this.value);
    console.log(name);
    console.log(age);
}

var bindFn = fn.myBind(obj, 'LJ', 25);

bindFn(); // 'hdove' 'LJ' 25
 
Copy the code

I think it looks fine, but let’s pass the parameters like this, okay


var bindFn = fn.myBind(obj, 'LJ');

bindFn(25); // 'hdove' 'LJ' undefined

Copy the code

We find that the parameters passed in later are missing, so we need to use corrification to solve this problem


Function.prototype.myBind = function(context) {

    if(typeof this ! = ="function") {
        throw new Error("Not a function."); } const self = this; // The first argument is this, intercepting const args1 = [...arguments].slice(1);return functionConst args2 = [...arguments]; () {const args2 = [...arguments];return self.apply(context, args1.concat(args2));
    }
}

const obj = {
  value: 'hdove'
}

function fn(name, age) {
    console.log(this.value);
    console.log(name);
    console.log(age);
}

var bindFn = fn.myBind(obj, 'LJ');

bindFn(25); // 'hdove' 'LJ' 25

Copy the code

3.3 this loss

One other feature of Bind is that it acts as a constructor, which means it can be used as a constructor. We can call the new operator to create an instance. When we use the new operator, this doesn’t actually point to the object we specified. Instead, it points to the constructor of the instance coming out of new, but the supplied argument list is still inserted before the argument list at the time of the constructor call. So let’s just do a simple implementation.


Function.prototype.myBind = function(context) {
    if(typeof this ! = ="function") {
        throw new Error("Not a function.");
    }
    const self = this;
    const args1 = [...arguments].slice(1);
    
    const bindFn = function() { const args2 = [...arguments]; /** here we can see by printing this. When the binding function is called as a normal function, this refers to the window. When used as a constructor, it refers to this instance, so this instanceofbindFn fortrue, this instance can get the values inside fn(). We can add a test attribute to fn. If undefined is printed, we can verify the problem we mentioned above. So the solution is to add validation to check the current this if this instanceofbindFn says this is a new instance, points to this instance, otherwise points to context */ console.log(this);return self.apply(this instanceof bindFn ? this : context, args1.concat(args2));
    }
    
    return bindFn;
}

const obj = {
  value: 'hdove'
}

function fn(name, age) {
    this.test = 'I'm test data';
    console.log(this.value);
    console.log(name);
    console.log(age);
}

var bindFn = fn.myBind(obj, 'LJ');

var newBind = new bindFn(25);

console.log(newBind.test); // undefined

Copy the code

3.4 Binding Prototypes

We all know that every constructor has a prototype object to add extra properties to.


function fn(name, age) {
    this.test = 'I'm test data';
}

fn.prototype.pro = 'Prototype data';

var bindFn = fn.myBind(obj, 'LJ', 25);

var newBind = new bindFn();

console.log(bindObj.pro); // undefined

Copy the code

Because we didn’t bind the prototype, undefined will appear, so let’s just bind it

Function.prototype.myBind = function(context) {
    if(typeof this ! = ="function") {
        throw new Error("Not a function.");
    }
    const self = this;
    const args1 = [...arguments].slice(1);
    
    const bindFn = function() {
        const args2 = [...arguments];
        return self.apply(this instanceof bindFn ? this : context, args1.concat(args2)); } // Bind the prototypebindFn.prototype = self.prototype;
    
    return bindFn;
}

function fn(name, age) {
    this.test = 'I'm test data';
}

fn.prototype.pro = 'Prototype data';

var bindFn = fn.myBind(obj, 'LJ', 25);

var newBind = new bindFn();

console.log(bindObj.pro); // "Prototype data"

Copy the code

But there’s a problem with that


function fn(name, age) {
    this.test = 'I'm test data';
}

fn.prototype.pro = 'Prototype data';

var bindFn = fn.myBind(obj, 'LJ');

var bindObj = new bindFn();

bindObj.__proto__.pro = 'Tamper with prototype data';

console.log(bindObj.__proto__ === fn.prototype); // true

console.log(bindObj.pro); // "Tampering with prototype data."

console.log(fn.prototype.pro); // "Tampering with prototype data."When we modifybindObj's prototype, fn's prototype was also modified becausebindObj.__proto__ === fn. Prototype we are working on itbindObj also modifies FN indirectlyCopy the code

The solution is simply to create a new method, proFn(), for stereotype binding, which is the original inheritance of several ways to implement inheritance, and then bind the instance object of this new method to the stereotype of our binding function

Function.prototype.myBind = function(context) {
    if(typeof this ! = ="function") {
        throw new Error("Not a function.");
    }
    const self = this;
    const args1 = [...arguments].slice(1);
    
    const bindFn = function() {
      const args2 = [...arguments];
       
        return self.apply(this instanceof bindFn ? this : context, args1.concat(args2)); } // Bind the prototypefunction proFn() {// create a new method profn. prototype = self.prototype; // Inherit the prototypebindFn.prototype = new proFn(); // Bind the prototypereturn bindFn;
}

function fn(name, age) {
    this.test = 'I'm test data';
}

fn.prototype.pro = 'Prototype data';

var bindFn = fn.myBind(obj, 'LJ', 25);

var newBind = new bindFn();

console.log(bindObj.__proto__ === fn.prototype); // false

console.log(bindObj.pro); // "Tampering with prototype data."

console.log(fn.prototype.pro); // "Prototype data"

Copy the code

Four, interview

These things are in fact easier to test the question in the interview, we do not think to recite, recite down is actually useless, easy to be asked, the key is to understand, understand can easily write out. Hopefully, this article will bring you something, even a little bit. Here, in advance to the big guy’s early years, lucky year of rat, job hopping, pay rise smooth, ha ha.

5. Recommended reading

  • Before you go home again, find someone new
  • Imitation netease Cloud music webApp
  • Taro + TS + Hook + MongoDB + KeystoneJS is the basis of Taro + TS + Hook + MongoDB + KeystoneJS