preface

This article contains 1630 words and takes about 8 minutes to read.

Summary: This paper simulates the call and apply methods from scratch by asking questions and then solving them

  • Function.prototype.call(), function.prototype.apply ()
  • Public account: “front-end advanced learning”, reply to “666”, get a package of front-end technology books

Every day that we don’t dance is a betrayal of life.

The body of the

The call, the apply

Let’s start with the Call and apply methods, which are both mounted on the prototype of the function, so all functions can call them.

Note: The call() method works like apply() except that call() takes a list of parameters, while apply() takes an array of parameters.

Example:

function foo(b = 0) {
	console.log(this.a + b);
}
const obj1 = {
	a: 1
};
const obj2 = {
	a: 2
};
foo.call(obj1, 1); / / 2
foo.call(obj2, 2); / / 4
foo.apply(obj1, [1]); / / 2
foo.apply(obj2, [2]); / / 4
Copy the code

If you are not familiar with this, you can start asynchronously: understand Javascript’s this. This refers to the caller. If no one calls this, it refers to undefined in strict mode and to window in non-strict mode.

So call and apply are essentially used to change the this value of the function being called. As mentioned above, call and apply differ only in parameters. The simulation implements Call, so apply is only a difference in parameter processing. In other words, call and apply do two things:

  1. Changes the value of the function being calledthisValue;
  2. Pass call;

# # # to change this

The problem of simulating call and apply now turns to the problem of how to change the this value of a function, which is quite simple:

function foo(b = 0) {
	console.log(this.a + b);
}
const obj1 = {
	a: 1.foo: foo
};
const obj2 = {
	a: 2.foo: foo
};
obj1.foo(1);
obj2.foo(2);
Copy the code

That is, we assign this method to the object, and then the object calls this function. Changing the this step of a function is simple. First, the function is assigned to the object to which this refers, and then the object calls the function. After execution, the function is removed from the object. The steps are as follows:

obj.foo = foo;
obj.foo();
delete obj.foo;
Copy the code

With this in mind we implement the first version of the Call method:

Function.prototype.call2 = function(context) {
  context = context || {};
  context[this.name] = this;
  context[this.name]();
  delete context[this.name];
}
Copy the code

This. name is the name of the function declaration, but it doesn’t have to correspond to the name of the function.

Function.prototype.call2 = function(context) {
  context = context || {};
  context.func = this;
  context.func();
  delete context.func;
}
Copy the code

Call the above function with the new call:

foo.call2(obj1); / / 1
foo.call2(obj2); / / 2
Copy the code

OK, this problem is solved, next is the problem of passing the parameter:

The ginseng

The arguments in the function are stored in an array-like object arguments. So we can take arguments from arguments from call2:

Function.prototype.call2 = function(context) {
  context = context || {};
  var params = [];
 	for (var i = 1; i < arguments.length; i++) {
    params[i - 1] = arguments[i];
	}
  context.func = this;
  context.func();
  delete context.func;
}
Copy the code

Now the question is, how do I pass params into func? The easy way to do this is to use ES6’s extension operators:

Function.prototype.call2 = function(context) {
  context = context || {};
  var params = [];
 	for (var i = 1; i < arguments.length; i++) {
    params[i - 1] = arguments[i];
	}
  context.func = this; context.func(... params);delete context.func;
}
Copy the code

Take a look at our example:

foo.call2(obj1, 1); / / 2
foo.call2(obj2, 2); / / 4
Copy the code

Another implementation uses the unusual eval function, where we concatenate arguments into a string and pass it to the eval function to execute.

The eval() function evaluates a string and executes the JavaScript code inside it.

Take a look at our second version implementation:

Function.prototype.call2 = function(context) {
  context = context || {};
  var params = [];
 	for (var i = 1; i < arguments.length; i++) {
    params[i - 1] = arguments[i];
	}
  // Note that this refers to the function being called
  context.func = this;
  eval('context.func(' + params.join(",") + ') ');
  delete context.func;
}
Copy the code

other

Call and Apply also have two other important features, which can return the result of a function execution normally, and point this to a window if null or undefined is accepted. Let’s implement these two features, and then add the necessary judgment hints. This is our third implementation:

Function.prototype.call2 = function(context) {
  context = context || window;
  var params = [];
  // I is initialized to 1 to skip the context argument
 	for (var i = 1; i < arguments.length; i++) {
    params[i - 1] = arguments[i];
	}
  // Note that this refers to the function being called
  context.func = this;
  var res = eval('context.func(' + params.join(",") + ') ');
  delete context.func;
  return res;
}
Copy the code

Then we call the test:

foo.call2(obj1, 1); / / 2

foo.call(2.1); // NaN
foo.call2(2.1); // context.func is not a function
Copy the code

As we found the object returned in the original call NaN, after changing the number 2 but our call2 error, illustrate a problem, we direct the context = context | | window is problematic. There is also an internal type judgment, and after solving this problem, our fourth version implementation looks like this:

Function.prototype.call2 = function(context) {  
  if (context === null || context === undefined) {
		context = window;
  } else {
		context = Object(context) || context;
  }
  var params = [];
  // I is initialized to 1 to skip the context argument
 	for (var i = 1; i < arguments.length; i++) {
    params[i - 1] = arguments[i];
	}
  // Note that this refers to the function being called
  context.func = this;
  var res = eval('context.func(' + params.join(",") + ') ');
  delete context.func;
  return res;
}
Copy the code

This is our final code, which is compatible from ES3 to ES6, at which point:

foo.call(2.1); // NaN
foo.call2(2.1); // NaN
Copy the code

Simulation implementation of Apply

Call2 = call2; call2 = call2;

Function.prototype.apply2 = function(context, arr) {
  if (context === null || context === undefined) {
		context = window;
  } else {
		context = Object(context) || context;
  }
  // Note that this refers to the function being called
  context.func = this;
  arr =  arr || [];
  var res = eval('context.func(' + arr.join(",") + ') ');
  delete context.func;
  return res;
}
Copy the code

So that’s our final implementation, but there’s still a problem with context.func, so we can’t pass a context with a func string as a method name.

conclusion

Our implementation solved the following problems:

  1. Changes the value of the function being calledthis;
  2. Passing arguments to the called function;
  3. Returns the result of the called function, with the first argument beingnullorundefinedWhen the function is calledthisPoint to thewindow;
  4. Solve the problem of type judgment;

The above.


The ability is limited, the level is general, welcome to erratum, greatly appreciated.

To subscribe for more articles, follow the public account “Front-end Advanced Learning” and reply to “666” for a package of front-end technology books