call

The call method calls a function or method by specifying a this and several parameters, for example

// e.g
const foo = {
  value: 'foo',}function bar() {
  console.log(this.value);
}

bar.call(foo); // foo
Copy the code

Analyze what Call did:

  1. Call changes the direction of this to foo
  2. Bar function execution

According to this idea, then we try to simulate the above two points

Function.prototype._call = function (target) {
  // Check whether target is null
  // If null, the execution context points to window
  const context = target || window;
  // Since arguments is an array of classes, add the form entry to the array
	// Notice that the loop starts from the second argument, because the first argument is target
  const args = [];
  for (let i = 1; i < arguments.length; i++) {
    args.push(`arguments[${i}] `);
  }
  // Notice that this is the function that currently calls call
  // Add the method to the context for execution
  context.fn = this;
  // Execute with eval as a string
  const result = eval(`context.fn(${args}); `);
  // Remove the method from context
  delete context.fn;
  return result;
}
Copy the code

Note: The number of passed parameters is uncertain. How can we solve this problem

// Since arguments are arrays of classes, we can use a for loop
for (let i = 1; i < arguments.length; i++) {
  args.push(`arguments[${i}] `);
}
/ / after execution, the args for [' the arguments [1] ', 'the arguments [2]',..., 'the arguments [I]']
Copy the code

With the variable argument problem solved, we place the parameter array in the parameters of the function to be executed. Since call is an ES3 method, in order to simulate implementing an ES3 method, we can use the eval method to assemble a function and execute it as follows:

eval(`context.fn(${args}); `)
Copy the code

Here args automatically calls array.toString (). The function returns a value, so it returns the result of the eval method.

const result = eval(`context.fn(${args}); `);
return result;
Copy the code

apply

As with Call, the only difference is that Apply takes an array as an argument, and the code is directly shown here

Function.prototype._apply = function (target, params) {
  const context = target || window;
  context.fn = this;
  if(! params) {return context.fn();
  }
  const args = [];
  for (let i = 0; i < params.length; i++) {
    args.push(`params[${i}] `);
  }
  const result = eval(`context.fn(${args}) `);
  delete context.fn;
  return result;
}
Copy the code

bind

MDN explain

Bind () creates a new method, and when the new function is called, the first argument to bind() will be its this at runtime, followed by a sequence of arguments that will be passed as its arguments before the arguments passed.

According to MDN, let’s take an example

const foo = {
  value: 'foo'
};

function bar(name) {
  console.log(this.value); // foo
  console.log(name); // elvis
}

const fbind = bar.bind(foo, 'elvis');
fbind();
Copy the code

Summarize the characteristics of BIND:

  1. Returns a new method
  2. You can pass in parameters

Simulate the implementation of the BIND method according to its characteristics

Function.prototype._bind = function (target) {
  const fn = this;
  const context = target || window;
  const args = Array.prototype.slice._call(arguments.1);
  return function () {
    const fnArgs = Array.prototype.slice._call(arguments);
    returnfn._apply(context, args.concat(fnArgs)); }}Copy the code

Note: the code uses the _call, _apply methods implemented above.

Here we go!!

The function created by the binding can also be called by the construct, in which case the this binding specified in Bind is invalidated, but the argument remains valid

A binding function can also use the new operator to create objects: this behavior is like treating the original function as a constructor. The supplied this value is ignored, and the arguments to the call are supplied to the mock function

For example, after calling native bind, you can see that this.value is undefined, indicating that the specified this is ignored.

const foo = {
  value: 'foo',}function bar(name, age) {
  this.job = "developer";
  console.log(this.value); // undefined
  console.log(name); // elvis
  console.log(age); / / 32
}

const fBind = bar.bind(foo, 'elvis');
const obj = new fBind('32');
console.log(obj.job); // developer
Copy the code

Based on the above results, we can do this by modifying the prototype of the return function

Function.prototype._bind = function (target) {
  const fn = this;
  const context = target || window;
  const args = Array.prototype.slice._call(arguments.1);
  const fBind = function () {
    const fnArgs = Array.prototype.slice._call(arguments);
    // When used as a constructor, this points to the instance
    // When called as a normal function, this points to the target specified by bind
    fn._apply(this instanceof fBind ? this : context, args.concat(fnArgs));
  }
  // Change the prototype of the return function to point to the prototype of the binding function so that you can access the value in the binding function prototype
  fBind.prototype = this.prototype;
  return fBind;
}
Copy the code

Optimization of construct calls

Prototype also changes the prototype of the binding function directly, which can be mediated by an empty function

Function.prototype._bind = function (target) {
  const fn = this;
  const context = target || window;
  const args = Array.prototype.slice._call(arguments.1);
  const f = function () {};const fBind = function () {
    const fnArgs = Array.prototype.slice._call(arguments);
    fn._apply(this instanceof f ? this : context, args.concat(fnArgs));
  }
  f.prototype = this.prototype;
  fBind.prototype = new f();

  return fBind;
}
Copy the code

So far, the big questions have been solved 👍🏻