js simulates call and apply

Introduction of call and apply

The call() method calls a function with a specified value and one or more parameters given separately.

The apply() method calls a function with a given value of this and the parameters provided as an array (or similar array object).

The grammar and function of this method are similar to that of apply(), except that the call() method accepts a list of parameters, while the apply() method accepts an array of multiple parameters.

II. Use of call

Call is the default function of Function. It is used in fn.call(context, arg0, arg1, arg2...). The context of the FN function becomes context, and the incoming parameters are also used.

var foo = { value: 1 };

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

fn.call(foo); // value => 1

3. Realization of analog call

To simulate the implementation of call, first understand what call does.

  • The context of fn changes only when call is used
  • The actual parameter of fn comes from the second parameter of call until the last parameter is
  • When the call method does not pass in the context, the global context is used by default.

The above three points are probably the behavior of call in non-strict mode.

Let's first look at how to change the context of fn.

Changing the context of fn requires only three steps:

  1. Open the refrigerator (hang FN on foo, named _fn)
  2. Elephants put in (foo._fn)
  3. Close the refrigerator (delete foo._fn)

The code is as follows

var foo = { 
    value: 1 
};

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

Function.propotype.myCall = function(that) {
    that._fn = this;
    that._fn();
    delete that._fn;
}

The first step is easy to complete and unhappy.~

Consider the case of parametric incoming

First we have to look at the collected parameters, and then pass them one by one to fn. After all, the number of parameters is not clear, so it's certainly better to use arguments to collect parameters. The problem is that when FN receives parameters, it is fn(context, arg0, arg1, arg2...). What can we do to change parameters into this form?

** Note: ** Parameters are not arrays, they are not collected and put directly in.

When the api is turned over, there are only two ways to meet the requirements:

  1. Using eval function
  2. Constructor using Function

The method of using eval is relatively simple, splicing parameters directly with strings, and then passing them into eval to run. But this method is not recommended officially, and there is a risk of malicious attack. Function is adopted here.

Function.prototype.myCall = function(that) {
    var target = this;
    var args = arguments, argArr = [];
    
    var args = arguments, argNameArr = [], argArr = [];
    for (var i = 0; i < args.length; i++) {
        argNameArr.push('arguments[1][' + i + ']');
        argArr.push(arguments[i]);
    }
    
    argNameArr.pop(); // Remove more arguments names
    argArr.shift(); // Remove the first parameter context
    
    that.fn = target; // Add function fn to that
    var result = new Function('fn', 'return fn(' + argNameArr.join(',') + ')')(that.fn, argArr); // Call the execution method immediately
    delete that.fn; // Remove function fn
    return result;
}

Considering the special case, when the incoming context is empty, the implementation code is as follows:

value = 111;

var foo = {
  value: 1
}

function bar(p1, p2) {
  console.log(this.value + ',' + p1 + ',' + p2);
}

Function.prototype.myCall = function (that) {
  var target = this;
  // If target is not a function
  if (typeof target !== 'function') {
    throw new TypeError(target + ' is not type of Function');
  }

  // If that is empty, assign the global this to that
  if (!that) {
    that = function() {
      return this;
    }()
  }

  // Getting parameters
  var args = arguments, argNameArr = [], argArr = [];
  for (var i = 0; i < args.length; i++) {
    argNameArr.push('arguments[1][' + i + ']');
    argArr.push(arguments[i]);
  }

  argNameArr.pop(); // Remove more arguments names
  argArr.shift(); // Remove the first parameter context

  that.fn = target; // Add function fn to that
  var result = new Function('fn', 'return fn(' + argNameArr.join(',') + ')')(that.fn, argArr); // Call the execution method immediately
  delete that.fn; // Remove function fn
  return result;
}

bar.myCall(foo, 2, 3);

In this way, the simulation implementation of a call is completed.

4. The Use of apply

Apply is a function implemented by default by Function, using fn.apply(thisArg, [argsArray]). The example code is as follows:

var foo = { value: 1 };

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

fn.apply(foo); // value => 1

5. Simulated implementation of apply

The only difference between apply and call is that the parameters received are different, so the only thing that needs to be changed is the processing of the parameters.

value = 111;

var foo = {
  value: 1
}

function bar(p1, p2) {
  console.log(this.value + ',' + p1 + ',' + p2);
}


Function.prototype.myApply = function (that) {
  var target = this;
  // If target is not a function
  if (typeof target !== 'function') {
    throw new TypeError(target + ' is not type of Function');
  }

  // If that is empty, assign the global this to that
  if (!that) {
    that = function () {
      return this; 
    }()
  }

  // The second parameter of apply is an array
  var args = arguments.length > 1 ? arguments[1] : [], argNameArr = [];

  // The stitching parameter names arguments[1][0], arguments[1][1]...
  for (var i = 0; i < args.length; i++) {
    argNameArr.push('arguments[1][' + i + ']');
  }

  // args is not an array type
  if (args instanceof Array) {
    that.fn = target;
    result = new Function('fn', 'return fn(' + argNameArr.join(',') + ')')(that.fn, args);
  } else {
    throw new ReferenceError('arguments 1 must be type of Array');
  }

  delete that.fn;
  return result;
}

bar.myApply(null, [2, 3]);

VI. SUMMARY

To get a deeper understanding of call and apply, you need to implement it, otherwise there are many places you can't think clearly. For example, how the context of a function changes. Only these two functions are implemented, and the results are as follows:

  1. How to get the global this
  2. Function constructor use
  3. Use of arguments parameters

Posted by copernic67 on Thu, 22 Aug 2019 06:24:36 -0700