Call method

Call Method Introduction

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

grammar

function.call(thisArg, arg1, arg2, ...)
Copy the code

Parameters that

ThisArg optional. The this value used when function is run. In non-strict mode, null or undefined is automatically replaced with a reference to a global object.

arg1, arg2, … Specifies the argument list

The return value

Call the return value of the function with the this value and arguments provided by the caller. If the method returns no value, undefined is returned.

OK, let’s look at an example

The sample

var hunter = {
  value: 1
}

function hunterFn () {
  console.log(this.value)
}

hunterFn.call(hunter) / / 1
Copy the code

The above example can be summed up as:

  1. call()The approach has changedthisPointing. Pointing to Hunter
  2. The hunterFn function executes.

Analog implementation

From the conclusion of the above example, how can this be referred to as change

Everyone knows the method in js, and whoever calls it points to this. With this in mind, let’s adjust the above sample code:

var hunter = {
  value: 1.hunterFn: function () {
    console.log(this.value)
  }
}
hunter.hunterFn() / / 1
Copy the code

Add the hunterFn method as an attribute in the Hunter object so that when the hunterFn method is called, since it is the hunterFn called by the Hunter object, this points to Hunter. This step is very easy.

There is an obvious downside to this approach. Changing the “this” pointer will add attributes to the Hunter object for no reason, which is definitely not possible, but can be easily resolved by simply using delete.

Therefore, the steps of simulating the Call method can be divided into the following three steps:

  1. willhunterFnMethod added ashunterObject properties
  2. Execute the function
  3. Delete the function
/ / the first step
hunter.func = hunterFn
/ / the second step
hunter.func()
/ / the third step
delete hunter.func
Copy the code

MyCall = MyCall; MyCall = MyCall; MyCall = MyCall

Function.prototype.MyCall = function (obj) {
  var context = obj
  context.func = this / / the first step
  context.func() / / the second step
  delete context.func / / the third step
}

var hunter = {
  value: 1
}

function hunterFn() {
  console.log(this.value)
}

hunterFn.MyCall(hunter) / / 1
Copy the code

The code above changes the “this” direction and looks easy.

When we introduced the call method at the beginning of this article, the call method also accepted arguments, and accepted a list of arguments (arg1, arg2…).

Let’s deal with the call method accepting arguments

Since the call method takes a list of arguments, and the number of arguments is not fixed, we can use the following tips to solve this problem.

argumentsIs an array-like object corresponding to the parameters passed to the function.argumentsThe arguments object is a local variable available to all (non-arrow) functions, and you can use the arguments object to refer to function arguments in a function. This object contains each argument passed to the function, the first at index 0.

Let’s print out what the arguments object contains

Function.prototype.MyCall = function (obj) {
  console.dir(arguments)
  / /... Omit some code
}
/ /... Omit some code

hunterFn.MyCall(hunter, 'The Forest is coming'.27)

// Arguments object contents are:
// arguments = {
// 0: {value: 1} => func
// 1: 'The forest is coming'
/ / 2:27.
// length: 3
// }
Copy the code

As you can see from the print above, the 0th argument is the object to which this points, and the arguments that follow are the ones we pass in, so we can take the second to last arguments from the arguments object and store them in an array. Since the Arguments object is a class array object, we can use a for loop to handle it.

// arguments = {
// 0: {value: 1} => func
// 1: 'The forest is coming'
/ / 2:27.
// length: 3
// }
var args = []

for (var i = 1, len = arguments.length; i < len; i++) {
  args.push(arguments[1])}// args => [' I ', 27]
Copy the code

The call method does not accept an Array, but a list of arguments. Would array. join be used to concatenate the Array? The answer is No. Join (1, 2, 3); join (1, 2, 3);

But we can use eval to do that, so let’s see how it works

Introduction of eval

The eavL () function executes the passed string as JavaScript code.

Grammar:

eval(string)
Copy the code

Parameter Description:

Is a string that represents a JavaScript expression, statement, or series of statements. Expressions can contain variables and attributes of existing objects.

The return value:

Returns the return value of the code in the string. If the return value is null, undefined is returned.

A simple example

function add(a, b) {
  console.log(a + b)
}

var arr = [1.2];

add(1.2) / / 3

eval("add(" + arr + ")")  / / 3
Copy the code

Now let’s adjust the MyCall method we just implemented as follows:

Function.prototype.MyCall = function (obj) {
  var context = obj
  var args = []

  for (var i = 1, len = arguments.length; i < len; i++) {
    args.push(arguments[i])
  }

  context.func = this / / the first step

  eval('context.func('+ args + ') ') / / the second step

  delete context.func / / the third step
}


var hunter = {
  value: 1
}

function hunterFn() {
  console.log(this.value)
}

hunterFn.MyCall(hunter, 'The Forest is coming'.27) // Uncaught ReferenceError: The tree is not defined
Copy the code

Run the above code, you will find that not as you thought, the result of the run error, what is the reason?

We can infer that the parameter must have been passed in, so we can print the array args and see the result:

// args => [" args ", 27]
Copy the code

(arguments[I]); (arguments[I]); (arguments[I]); Func (context.func(tree advent, 27))). If the tree advent parameter is not quoted, it becomes a variable. If the tree advent parameter is not defined, the tree advent parameter is not defined

Once you understand what the problem is, it’s easy to fix it. In the for loop, the code changes as follows:

for (var i = 1, len = arguments.length; i < len; i++) {
  args.push('arguments[' + i + '] ')}Copy the code

[“arguments[1]”,”arguments[2]”]

Readjust the MyCall code as follows:

Function.prototype.MyCall = function (obj) {
  var context = obj
  var args = []

  for (var i = 1, len = arguments.length; i < len; i++) {
    args.push('arguments[' + i + '] ') 
  }

  context.func = this / / the first step

  eval('context.func('+ args + ') ') / / the second step

  delete context.func / / the third step
}


var hunter = {
  value: 1
}

function hunterFn() {
  console.log(this.value)
}

hunterFn.MyCall(hunter, 'The Forest is coming'.27) / / 1
Copy the code

This is not the complete version of the call method. There are still two things that are not implemented in this code. This is a trivial matter.

  1. Specified asnullorundefinedIs automatically replaced with pointing to a global object
  2. A function can have a return value

The above two problems are very easy to solve, specific I directly explain in the code comments, we directly on the final version of the code!

Function.prototype.MyCall = function (obj) {
  var context = obj || window // If obj does not exist, point directly to window
  var args = []

  for (var i = 1, len = arguments.length; i < len; i++) {
    args.push('arguments[' + i + '] ') 
  }

  context.func = this 

  var result = eval('context.func('+ args + ') ') // Save the execution result

  delete context.func 

  return result // Return the func method
}


var hunter = {
  value: 1
}

var value = Awesome!

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

hunterFn.MyCall(hunter, 'The Forest is coming'.27) // The forest comes 27 1
hunterFn.MyCall(null.'The Forest is coming'.27) // The forest world descends 27 666
hunterFn.MyCall(undefined.'The Forest is coming'.27) // The forest world descends 27 666
Copy the code

The amount of code above seems a bit excessive, but it would be simpler if it could be implemented using ES6:

The CALL method is simulated in ES6 mode

Function.prototype.MyCall = function (obj) {
  let context = obj || window
  let args = [...arguments].slice(1)  // Use the expansion operator to process arguments objects into arrays
  context.func = this 
  varresult = context.func(... args)delete context.func 
  return result
}
Copy the code

Now that the emulation of the Call method is complete, let’s look at the emulation of the Apply method.

The apply method

Introduction to the Apply method

The apply() method calls a function with a given this value and arguments in the form of an array (or array-like object).

grammar

function.apply(thisArg, [argsArray])
Copy the code

Parameters that

ThisArg is mandatory. The this value used when function is run. In non-strict mode, null or undefined is automatically replaced with a reference to a global object, and the original value is wrapped.

ArgsArray optional. An array or array-like object whose array elements are passed as individual arguments to function. If the value of this parameter is null or undefined, no arguments need to be passed in.

The return value

The result of calling a function with the specified this value and argument.

From the above, you can see that the apply() method is very similar to the call() method, except for the way arguments are provided. Let’s look at an example of the apply() method:

The sample

// Use the original example above, with some minor changes
var hunter = {
  value: 1
}

function hunterFn (arg1, arg2) {
  console.log(arg1, arg2, this.value)
}

hunterFn.apply(hunter, ['The Forest is coming'.'27']) // The forest comes 27 1

Copy the code

In fact, understand the call method implementation principle, then the implementation of the simulation of apply method is very simple, here is no longer detailed step-by-step explanation, directly in the MyCall method of the final version of the code can be changed. The code is as follows:

Emulation implements the final version of the Apply method code

Function.prototype.MyApply = function (obj, arr) {
  var context = obj || window 
  var result;
  context.func = this 
  // Check whether the arr argument exists
  if(! arr) { result = context.func() }else {
    var args = []
    // Note that the index starts at 0
    for (var i = 0, len = arr.length; i < len; i++) {
      args.push('arr[' + i + '] ') 
    }
    result = eval('context.func('+ args + ') ') // Save the execution result
  }
  delete context.func 
  return result 
}


var hunter = {
  value: 1
}

var value = Awesome!

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

hunterFn.MyApply(hunter, ['The Forest is coming'.27]) // The forest comes 27 1
hunterFn.MyApply(null['The Forest is coming'.27]) // The forest world descends 27 666
hunterFn.MyApply(undefined['The Forest is coming'.27]) // The forest world descends 27 666
Copy the code

The APPLY method is simulated in ES6 mode

Function.prototype.MyApply = function (obj, arr) {
  let context = obj || window 
  let result;
  context.func = this 
  // Check whether the arr argument exists
  if(! arr) { result = context.func() }else{ result = context.func(... arr) }delete context.func 
  return result 
}

Copy the code

Let’s look at another way to change the direction of this, the bind() method

The bind method

The bind method

The bind() method creates a new function, and when bind() is called, this of the new function is specified as the first argument to bind(), and the remaining arguments will be used as arguments to the new function.

grammar

function.bind(thisArg[, arg1[, arg2[, ...]]])
Copy the code

Parameters that

ThisArg the value passed to the target function as this when calling the binding function. If the new operator is used to construct the binding function, this value is ignored. When using bind to create a function in setTimeout (provided as a callback), any raw values passed as thisArg are converted to Object. If bind’s argument list is empty, or thisArg is null or undefined, this executing the scope will be treated as thisArg for the new function.

arg1, arg2, … Arguments that are preset into the argument list of the binding function when the target function is called.

The return value

Returns a copy of the original function with the specified this value and initial arguments.

The sample

OK, let’s look at a simple example of the bind method in use:

var hunter = {
  value: 1
}

function hunterFn () {
  console.log(this.value)
}

var fn = hunterFn.bind(hunter)

fn() / / 1
Copy the code

From the above introduction of BIND, we can draw the following conclusions:

  1. You can modify the function’s this pointer
  2. A function is returned and will not be executed immediately
  3. Returns a value
  4. You can pass in parameters
  5. Support for currification of a function, that is, when a new function is returned, a number of parameters can be passed in addition to the parameters when the new function is called (can be understood as a step-by-step process of processing parameters).
  6. If the new operator is used to construct the binding function, this value is ignoredThat is, when the new function returned by bind is used as a constructor,bind()The this parameter is invalidated, but the argument remains valid.

Based on the characteristics of the bind method summarized above, we will simulate it step by step.

In the first point, we can use call or apply to change the direction of this. The above simulation has just been implemented. In the second point, it is not difficult to return a function, we can just return a function. Let’s take a look at the code:

Function.prototype.MyBind = function (context) {
  var _this = this
  // Return a function
  return function () {
    / / the return value
    return _this.apply(context) // Use the apply method to change this pointer}}Copy the code

This version of the code is not very easy! Let’s move on to implement the fourth and fifth points.

For the fourth, it is very easy to accept a parameter, and for the fifth, it is necessary to implement the function Currization. This is a bit complicated, but it is not difficult. Let’s take a look at the introduction of Currization.

The function is currified

The technique of converting a function that takes multiple arguments into a function that takes a single argument (the first argument of the original function) and returns a new function that takes the remaining arguments and returns the result. The simplest way to say it is to take a function with many parameters, and turn it into a function with one parameter.

A simple example

The following code is a classic example of a currization of a function

function add(x, y) {
  return function (y) {
    console.log(x + y)
  }
}
var _add = add(1)
_add(1) / / 2

add(1) (1) / / 2
Copy the code

With a brief overview of function currification, let’s recalibrate the previous version of the code:

Function.prototype.MyBind = function (context) {
  var _this = this
  / / 1
  var args = Array.prototype.slice.call(arguments.1)
  return function () {
    / / 2
    var _args = Array.prototype.slice.call(arguments)
    / / 3
    return _this.apply(context, args.concat(_args)) 
  }
}
Copy the code

So let’s explain this code,

  • 1

Var args = Array. Prototype. Slice. Call (1) the arguments, because the zeroth the arguments object is this, so we start cutting from index is 1.

  • 2

Var _args = Array. Prototype. Slice. The call (the arguments) in the second call, get all the parameters, at this time there is no this, send it so we all received ^_^.

  • 3

Return _this.apply(context, args. Concat (_args)) return _this.apply(context, args.

Based on the version of code we implemented above, let’s verify that we can satisfy points 1 through 5.

// Test the code

Function.prototype.MyBind = function (context) {
  var _this = this
  var args = Array.prototype.slice.call(arguments.1)
  return function () {
    var _args = Array.prototype.slice.call(arguments)
    return _this.apply(context, args.concat(_args))
  }
}

var hunter = {
  value: 1
}

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

var fn = hunterFn.bind(hunter, 'The Forest is coming')

fn(27) // The forest comes 27 1

Copy the code

When the new function is used as a constructor, the bound this value is invalidated, but the argument is still valid. Let’s look at the effect:

var value = Awesome!
var hunter = {
  value: 1
}

function hunterFn (name, age) {
  this.details = 'to do'
  console.log(this.value, name, age)
}

hunterFn.prototype.music = '1, 2, 3, 4,'

var Fn = hunterFn.bind(hunter, 'The Forest is coming')

var obj = new Fn(27) // undefined "undefined" 27

console.log(obj.details) // to do
console.log(obj.music) // 1, 2, 3, 4
Copy the code

In hunterFn, the value is undefined, even if var value = 666 is added to the outermost layer, it is still undefined, indicating that this pointer is invalid. Meanwhile, we print the obj attribute at the bottom. When the constructor is called by new, it essentially creates an instance, and this inside the function points to the instance being created. When the code executes console.log(this.value,…) “, the current instance does not have a value attribute, resulting in the print of undefined.

So if we want to implement this feature, we need to distinguish between whether a function is called by a new function and whether it is called by a normal function, and then it’s a little bit easier.

To tell which of the two functions the function is called from, use either instanceof (whether it is in the prototype chain) or constructor (who is the constructor pointing to

Let’s try to change the above code:

  1. Use the Instanceof mode
Function.prototype.MyBind = function (context) {
  var _this = this
  var args = Array.prototype.slice.call(arguments.1)
  var boundFn = function () {
    var _args = Array.prototype.slice.call(arguments)
    // 1: Determines if the current this is in boundFn when called by new, and if true,
    // Then point this of the binding function to the instance, so that the instance can get the value of the binding function (e.g. details property).
    // 2: when called by a normal function, the binding function's this points to the context.
    return _this.apply(this instanceof boundFn ? this : context, args.concat(_args))
  }
  // Prototype inheritance
  boundFn.prototype = this.prototype
  return boundFn
}

var value = Awesome!
var hunter = {
  value: 1
}

function hunterFn (name, age) {
  this.details = 'to do'
  console.log(this.value, name, age)
}

hunterFn.prototype.music = '1, 2, 3, 4,'
var Fn = hunterFn.MyBind(hunter, 'The Forest is coming')

var obj = new Fn(27) // undefined "undefined" 27

console.log(obj.details) // to do
console.log(obj.music) // 1, 2, 3, 4
Copy the code
  1. Use the constructor approach
// Use the instanceof method
Function.prototype.MyBind = function (context) {
  var _this = this
  var args = Array.prototype.slice.call(arguments.1)
  var boundFn = function () {
    var _args = Array.prototype.slice.call(arguments)
    // This is slightly different from the above method
    return _this.apply(this.constructor === _this ? this : context, args.concat(_args))
  }
  // Prototype chain inheritance
  boundFn.prototype = this.prototype
  return boundFn
}
// This method produces the same result as above, so there is no extra space to show it here.
Copy the code

BoundFn. Prototype = this.prototype (); boundFn. Prototype (); The prototype value of this.prototype (binding function) is also modified directly. Second, we need to give an error if the bound value is something other than a function.

Let’s fix the error message first, this is easier, directly on the code:

Function.prototype.MyBind = function (context) {

  if (typeof this! = ='function') {
    throw new Error('Binding type not function~')}var _this = this
  var args = Array.prototype.slice.call(arguments.1)
  var boundFn = function () {
    var _args = Array.prototype.slice.call(arguments)
    return _this.apply(this instanceof boundFn ? this : context, args.concat(_args))
  }
  boundFn.prototype = this.prototype
  return boundFn
}
Copy the code

For modifying the content of the returned new function prototype will affect the content of the binding function prototype, how to modify, if you understand several kinds of JS inheritance, it is not difficult to solve this problem, not too understand can see this article JS five kinds of inheritance

We can use a blank function to convert it, that is, the implementation of parasitic composite inheritance, directly to the final version of the code

Mock implementation of bind method code final version

Function.prototype.MyBind = function (context) {
  if (typeof this! = ='function') {
    throw new Error('Binding type not function~')}var _this = this
  var args = Array.prototype.slice.call(arguments.1)
  var boundFn = function () {
    var _args = Array.prototype.slice.call(arguments)
    return _this.apply(this instanceof boundFn ? this : context, args.concat(_args))
  }
  // Use a blank function to convert method 1
  // var TempFn = function () {}
  // TempFn.protoType = this.prototype
  // boundFn.protoType = new TempFn()

  // Use blank function to convert method 2
  boundFn.prototype = Object.create(this.prototype)
  boundFn.prototype.constructor = boundFn
  return boundFn
}
Copy the code

New operator

Introduction to the new operator

The new operator creates an instance of a user-defined object type or of a built-in object with a constructor.

grammar

  new constructor[([arguments])]
Copy the code

Parameters that

Constructor A class or function that specifies the type of an object instance.

Arguments A list of arguments to be called by constructor.

describe

  1. Create an empty simple JavaScript object (that is {});
  2. Linking this object (setting its constructor) to another object;
  3. Use the new object created in Step 1 as the context for this;
  4. If the function does not return an object, this is returned.

According to the effect described in MDN above, it is not ugly, and its simple operation process looks like this:

var People = function (name, age) {
  // Create an object this = {}
  // Assign an attribute to the object to which this points
  this.name = name
  this.age = age
  If this is not returned manually, the object referred to by this is returned by default
}

var person = new People('hunter'.18)
Copy the code

Let’s look at what the real new operator does

The sample

function People (name, age) {
  this.name = name
  this.age = age
}

People.prototype.sayHello = function () {
  console.log(Hi `The ${this.name}! `)}var person = new People('hunter'.18)

console.log(person.name, person.age) // hunter 18
person.sayHello() // Hello Hunter!
Copy the code

As you can see from the example above, instance Person has access to the name and age properties of the People constructor, as well as to the properties in the stereotype. With these characteristics in mind, let’s start simulating a simple new method! Because the new operator method we simulated cannot be directly overridden like the methods mentioned above, we implemented a function to simulate the new operator. The implementation idea is as follows:

  • Creates an object based on the prototyp property of the constructor
  • Use the call or apply method to pass this and the call parameters to the constructor
  • If the constructor does not return an object manually, the object created in the first step is returned by default

Simulation implementation of the new method code final version

var simulateNew = function (Parent, ... args) {
  // Create an object with the properties above the constructor prototype
  var child = Object.create(Parent.prototype)
  // Using apply, change the reference of the constructor this to the newly created object so that the child can access the attributes in the constructor
  var result = Parent.apply(child, args)
  If no object is returned manually, child is returned by default
  return typeof result === 'object' ? result || child : child
}
Copy the code

This is all the content of this article, looking at the feeling is very long, in fact, down the train of thought is not particularly difficult to understand. Please point out any mistakes.

Refer to the article

developer.mozilla.org/zh-CN/… / n…

developer.mozilla.org/zh-CN/… / a…

developer.mozilla.org/zh-CN/… / c…

Github.com/mqyqingfeng…

Github.com/mqyqingfeng…

Github.com/mqyqingfeng…

www.cnblogs.com/echolun/p/1…

www.cnblogs.com/echolun/p/1…