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:
- Changes the value of the function being called
this
Value; - 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:
- Changes the value of the function being called
this
; - Passing arguments to the called function;
- Returns the result of the called function, with the first argument being
null
orundefined
When the function is calledthis
Point to thewindow
; - 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