Implementation of call,apply,bind and new

Keywords: Javascript Attribute

call

First of all, let's look at the usage of call (a call)

call principle: let the previous function execute and change the direction of this. If the first parameter is not passed or is null or undefined, then this is window. If passed, this is the first parameter

 function fn1(n,m) {
        console.log('I am fn1',n,m);//I'm fn1 1 2
    }
    function fn2() {
        console.log('I am fn2');
    }
    fn1.myCall(fn2,1,2) 

The above example is very clear, so let's implement the call method

Function.prototype.myCall = function (context) {
 //If a parameter is passed, it will be directly converted into an object (fault tolerance processing, if it is a number, an error will be reported, 1.fn). If it is not passed, it will be window
        context = context ? Object(context) : window;
 //If the syntax of direct this=context js is not supported, adding a fn attribute to context and sharing the same address with this is the same as letting the function calling call execute and making context as his this
        context.fn = this;
        let ary = [];
        for (let i = 1; i < arguments.length; i++) {
            ary.push(arguments[i])
        }
        //Using string splicing transformation to convert array into string and pass in sequence
        let r = eval('context.fn(' + ary + ')');
        delete  context.fn;
         return r;
    };
Analysis of two or more call s
 function fn1(n, m) {
        console.log(this);
        console.log('I am fn1', n, m);
    }

    function fn2(n,m) {
        console.log('I am fn2',n,m);//I'm FN2 undefined
    }
    
 fn1.myCall.myCall(fn2, 1, 2)
//In the case of two or more mycalls, the myCall method on the class is called, and the first parameter is passed in (here is FN2), then myCall(fn2) will change this point of myCall into FN2 and execute the myCall method. When myCall is executed, it is equivalent to fn2.myCall(1,2), where this of FN2 is object 1, If the value after 1 is passed in as a parameter, the above result will be obtained (I am fn22 undefined)

apply

The only difference is that the parameter must be an array
Usage:

 function fn1(n, m) {
        console.log('I am fn1', n, m);//I'm fn11 2
    }

    function fn2(n,m) {
        console.log('I am fn2',n,m);
    }
    
fn1.myApply(fn2,[1,2])

realization

//If the array is specified here, the formal parameter will be used to receive it directly
  Function.prototype.myApply = function (context,ary) {
        context = context ? Object(context) : window;
        context.fn = this;
        //Determine whether to pass parameters. If not, directly return the executed function
        if(!ary){
            return context.fn()
        }
        let r = eval('context.fn(' + ary + ')');
        return r;
        delete  context.fn;
    };

new

The keyword new actually performs the following steps when calling the constructor:
1. An object is created
2.this points to instance
3. Execute the code in the constructor to add properties to the object
4. Return the new object
!!! If the class returns a reference data type, it will directly return the reference data type value after the return. If the basic data type is returned, the return value will not be affected
Now let's look at the following example of native new

 function Animal(type, a) {
        this.type = type;
        this.a = a;
       // return {name: 'Meng Yuhang'}
    }

    Animal.prototype.say = function () {
        console.log('say');
    };

    let animal = new Animal('animal', 'cat');
    console.log(animal);
    //The printed contents are as follows:
        Animal {type: "animal", a: "cat"}
        __propto__: say:f()
        constructor: ƒ Animal(type, a)

So we transform new into this one to achieve the same function:

let animal = myNew(Animal,'animal', 'cat');
  //First, create a simple class
    function Animal(type, a) {
        this.type = type;
        this.a = a
        // return {name: 'Meng'}
    }

    Animal.prototype.say = function () {
        console.log('say');
    };

    function myNew() {
        //Delete and get the first item of arguments
        let c = [].shift.call(arguments);
        //new return object
        let obj = {};
        //Points to the public property of the class
        obj.__proto__ = c.prototype;
        //Execute this class and take obj as this, passing the parameter that has intercepted the first item
        let r = c.apply(obj, arguments);
        //If the returned data type is a reference data type, it will be returned directly, otherwise the original value will be returned
        return r instanceof Object ? r : obj
    }
    let animal = myNew(Animal, 'animal', 'mao');
    console.log(animal);

In this way, we implement the same function as new

bind

Use of bind:

1.bind returns a function
 2.bind changes the caller's this direction
 3.bind function can be new
Application and implementation without parameter transfer

Application:

let obj={name:'myh'};

  function fn() {
      console.log(this.name); //myh
  }
  let bindFn = fn.bind(obj)
  bindFn()

Implement bind without passing parameters

Function.prototype.myBind=function(context){
      //Use the closure to save the caller. Here is fn. If you don't write this step, then this is window when bindFn()
      let that = this;
      return function () {
   //The reason why apply is used is that it is convenient to handle the transfer of parameters
          that.apply(context)
      }
  };

The scenario of no parameter transfer has been implemented above. Let's take a look at the scenario of parameter transfer

bind implementation under transfer parameters

Let's start with the native example

 let obj={
      name:'myh'
  };
  function fn(age,animal) {
      console.log(this.name+'One'+age+'Year old'+animal);
      //myh has a 3-year-old kitten
  }
  let bindFn = fn.bind(obj,3);
  bindFn('little cat')

Implementation of transfer parameters

  Function.prototype.myBind=function(context){
      let that = this;
      let bindArgs = [].slice.call(arguments,1);//[3]
      return function () {
          let args = [].slice.call(arguments);//["kitten"]
          that.apply(context,bindArgs.concat(args))
      }
  };
bind with new

Let's take a look at the following code analysis. this of the new function points to the instance

  let obj={
      name:'myh'
  };
  function fn() {
  //According to our implementation above, this points to obj. If it is a new function, we need to change this here to make this point to the instance
        console.log(this);
        this.say= 'speak'
  }
  let bindFn = fn.bind(obj);
  let newBindFn = new bindFn();
  console.log(newBindFn);

The implementation is pointed to by the new function this

  function fn() {
        console.log(this);
        this.say = 'speak'
    }
Function.prototype.myBind = function (context) {
        let that = this;
        let bindArgs = [].slice.call(arguments, 1);
        function boundFn() {
            let args = [].slice.call(arguments);
     //Determine whether it is an instance of the boundFn class
            return that.apply(this instanceof boundFn ? this : context, bindArgs.concat(args))
        }
        return boundFn
    };
    
 let bindFn = fn.myBind(obj);
    let newBindFn = new bindFn();
    console.log(newBindFn);

Inheritance of prototype methods

If a method is added to the prototype of fn function, the__ proto__ It does not point to the prototype of the class, so modify it as follows:

 let obj = {
        name: 'myh'
    };

    function fn() {
        this.say = 'speak'
    }

    Function.prototype.myBind = function (context) {
        let that = this;
        let bindArgs = [].slice.call(arguments, 1);

        function Fn() {}//Create an intermediate class
        function boundFn() {
            let args = [].slice.call(arguments);
            return that.apply(this instanceof boundFn ? this : context, bindArgs.concat(args))
        }
        Fn.prototype = this.prototype;
        //prototype cannot be assigned directly to an object, so the same reference address will be used
        boundFn.prototype = new Fn();
        return boundFn
    };
    fn.prototype.flag = 'Mammals';
    let bindFn = fn.myBind(obj);
    let newBindFn = new bindFn();
    console.log(newBindFn);

Posted by fdost on Mon, 29 Jun 2020 01:20:31 -0700