Javascript simulation implementation call,apply

Introduction to call and apply

First, let's introduce the call and apply methods. These two methods are mounted on the function prototype, so all functions can call these two methods.

Note: the function of the call() method is similar to that of the apply() method, except that the call() method accepts a parameter list, while the apply() method accepts 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

Students who are not familiar with this can first understand this in JavaScript. To sum up, this of a JavaScript function points to the caller, and whoever calls this points to who. If no one calls this function, it points to undefined in strict mode and window in non strict mode.

So essentially, call and apply are used to change the this value of the called function. As mentioned above, call and apply only have different parameters. If call is simulated, then apply is only the difference in parameter processing. In other words, call and apply do two things:

  1. Change the value of this of the called function;
  2. Pass parameter call;

###Change this

Now the problem of simulating the implementation of call and apply is transferred to another problem, that is, how to change the this value of a function. It is very 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);

That is, we assign this method to the object, and then the object calls this function. The step of changing this of a function is very simple. First assign the function to the object to which this is pointing, and then the object calls the function. After execution, delete the function from the object. The steps are as follows:

obj.foo = foo;
obj.foo();
delete obj.foo;

With ideas in mind, we implement the first version of call method:

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

this.name is the name of the function declaration, but it is not necessary to correspond to the function name. We can use any key:

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

Call the above function with the new call:

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

OK, the problem of this is solved, and then the problem of parameter transmission:

 

Chuan Shen

The parameters in the function are stored in a class array object arguments. Therefore, we can get the parameters passed from arguments to 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;
}

At this point, the problem arises. How to pass the parameter params to func? The easy way to think of is to use the extension operator of ES6:

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;
}

Let's look at our example:

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

Another implementation is to use the rarely used Eval function, that is, we splice the parameters into a string and pass it to the eval function for execution,

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

Take a look at our second edition 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 here refers to the called function
  context.func = this;
  eval('context.func(' + params.join(",") + ')');
  delete context.func;
}

 

other

call and apply also have two other important features. They can return the execution result of the function normally. When null or undefined is accepted as a parameter, point this to window. Then we will implement these two features and add necessary judgment tips. This is our third version implementation:

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

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

As mentioned above, we found that after changing the object to the number 2, the original call returned NaN, but our call 2 reported an error, indicating a problem. There is a problem with our direct context = context | window. There is also an internal type judgment. After solving this problem, our fourth version is implemented as follows:

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

This is our final code, which can be compatible from ES3 to ES6. At this time:

foo.call(2, 1); // NaN
foo.call2(2, 1); // NaN

Resource search website Encyclopedia https://www.renrenfan.com.cn Guangzhou VI design companyhttps://www.houdianzi.com

Simulation Implementation of apply

apply and call are only parameter differences. Just rewrite call2:

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

The above is our final implementation. At present, another problem is context The problem of func. In this way, the context we pass in cannot use func string as the method name.

 

conclusion

Our implementation process has solved the following problems:

  1. Change this of the called function;
  2. Pass the parameters to the called function;
  3. Return the result of the called function. When the first parameter is null or undefined, this of the called function points to window;
  4. Solve the problem of type judgment;

Tags: Javascript

Posted by run2web on Tue, 03 May 2022 23:49:32 +0300