preface
Hi, I’m Wakawa. This is the third part of the interviewers’ questions series, which aims to help readers improve the basic knowledge of JS, including new, call, apply, this, inheritance related knowledge. The interviewer asked the following article series: Interested readers can click here to read it. Q: Can you simulate the JS new operator? Q: can you simulate the JS call and apply methods? Interviewer: JS inheritance
I have written two previous articles, “Interviewer Asks: Can YOU Emulate JS New Operator” and “Interviewer Asks: Can you Emulate JS Bind”.
Call and apply are used to modify this. But the interviewer might ask if you can do this without call and apply. We need to simulate the implementation of Call and apply.
There are already many articles that simulate call and Apply, so why do I need to write it again? Learning is like a mountain. People climb the mountain along different paths and share the scenery they see. You may not be able to see the scenery others see, feel the mood of others. Only oneself go climbing, can see different scenery, experience is more profound.
Through the firstMDN
Under the understandingcall
andapply
Function.prototype.call() ¶
fun.call(thisArg, arg1, arg2, ...)
Copy the code
ThisArg Specifies the value of this when fun is run. Note that the value specified is not necessarily the actual value of this when the function is executed. If the function is in non-strict mode, null and undefined this values are automatically referred to the global object (which in the browser is the window object) and have the original values (numbers, strings, Boolean) points to an autowrap object for the original value. arg1, arg2, … The return value of the specified argument list is the return value of the method you called, or undefined if the method does not return a value.
Function.prototype.apply()
func.apply(thisArg, [argsArray])
Copy the code
ThisArg is optional. The value of this used when the func function is run. Note that this may not be the actual value that the method sees: if the function is in non-strict mode, null or undefined is automatically replaced with a reference to the global object, and the original value is wrapped. ArgsArray is optional. An array or array-like object whose array elements are passed as separate arguments to the func function. If the value of this parameter is null or undefined, no parameters need to be passed. Array-like objects are available starting with ECMAScript 5. Return Value The result of a call to a function with a specified this value and argument. Let’s go straight to example 1
call
和 apply
The similarities and differences
Similarities: 1. Call and Apply’s first argument, thisArg, are both this specified by the func runtime. Also, this may not be the actual value that the method sees: if the function is in non-strict mode, null or undefined is automatically replaced with a reference to the global object, and the original value is wrapped. 2, can pass only one argument. Differences: Apply only takes two arguments. The second argument can be an array or a class array, or an object. Subsequent arguments are ignored. The call takes a second and subsequent set of parameters. Look at two simple examples 1 and 2** :
// Example 1: var in non-strict modedoSth = function(a, b){
console.log(this);
console.log([a, b]);
}
doSth.apply(null, [1, 2]); // this is window // [1, 2]doSth.apply(0, [1, 2]); // this is Number(0) // [1, 2]doSth.apply(true); / / this is a Boolean (true) // [undefined, undefined]
doSth.call(undefined, 1, 2); // this is window // [1, 2]doSth.call('0', 1, {a: 1}); / / this is a String ('0') // [1, {a: 1}]
Copy the code
// Example 2: Browser environment in strict mode'use strict';
var doSth2 = function(a, b){
console.log(this);
console.log([a, b]);
}
doSth2.call(0, 1, 2); // this is 0 // [1, 2]doSth2.apply('1'); / / this is'1' // [undefined, undefined]
doSth2.apply(null, [1, 2]); // this is null // [1, 2]Copy the code
Typeof (undefined number string Boolean symbol object function) First, in strict mode, the value of this is the first argument to call and apply. In non-strict mode, when thisArg is null or undefined, the value of this is automatically replaced with a reference to the global object, and the original value is automatically wrapped. That is new Object().
If you look at call and apply again, you’ll see that they both work the same way. Change this to refer to the first argument, thisArg. If it’s clear how many arguments there are, you can use call. You can use apply instead of call. In other words, we only need to simulate apply. Call can be put in an array based on the number of parameters and given to apply.
Analog implementationapply
Now that you’re ready to emulate apply, you need to look at the ES5 specification. ES5 Specification English version, ES5 Specification Chinese version. The next specification for Apply is the call specification, which can be viewed by clicking on the new TAB page. Here is a copy of it.
When calling the apply method on a func object with thisArg and argArray as arguments, do the following:
1. If IsCallable(func) is false, raise a TypeError. 2. If argArray is null or undefined, return the result of calling func’s [[Call]] internal method with an empty argument list and providing thisArg as this value. Return the result of calling func’s [[Call]] internal method with an empty argument list and providing thisArg as this value. 4. If Type(argArray) is not Object, a TypeError is raised. 9. Provide thisArg as this value and argList as the argument list. Call func’s internal method [[Call]] and return the result. The length attribute of the apply method is 2.
The thisArg value passed in from the outside is modified to become this. When thisArg is undefined or NULL it is replaced with a global object, and all other values are applied ToObject and the result is this, a change introduced in version 3.
In combination with the above and the specification, how to refer to this in the function to the first argument, thisArg, is a problem. Here’s example 3:
Var if the browser environment is not strict modedoSth = function(a, b){
console.log(this);
console.log(this.name);
console.log([a, b]);
}
var student = {
name: 'if the sichuan'.doSth: doSth,
};
student.doSth(1, 2); // this === student // true // 'if the sichuan'/ / [1, 2]doSth.apply(student, [1, 2]); // this === student // true // 'if the sichuan'/ / [1, 2]Copy the code
If you add a function doSth to the student object and execute the function, this will refer to the object. That is, you can add a new call function to thisArg and delete it after execution. With this in mind, we tried to make the first version easy to implement:
// The browser environment is not strict modefunction getGlobalObject() {return this;
}
Function.prototype.applyFn = functionApply (thisArg, argsArray){// The 'length' attribute of the 'apply' method is' 2 '. // 1. If 'IsCallable(func)' is'false, a TypeError exception is raised.if(typeof this ! = ='function'){
throw new TypeError(this + ' is not a function'); } // 2. If argArray is null or undefined, then // returns the result of calling func's [[Call]] internal method with an empty argument list and providing thisArg as this value.if(typeof argsArray === 'undefined'|| argsArray === null){ argsArray = []; } // 3. If Type(argArray) is not Object, raise TypeError.if(argsArray ! == new Object(argsArray)){ throw new TypeError('CreateListFromArrayLike called on non-object');
}
if(typeof thisArg === 'undefined'| | thisArg = = = null) {/ / incoming outside thisArg value will change and become this value. // ES3: when thisArg is undefined or null, it will be replaced by window thisArg = getGlobalObject(); } // ES3: All other values are applied ToObject and the result is this, a change introduced in version 3. thisArg = new Object(thisArg); var __fn ='__fn'; thisArg[__fn] = this; Var result = thisArg[__fn](... argsArray); delete thisArg[__fn];return result;
};
Copy the code
After the first release was implemented, it was easy to identify two problems:
- 1.
__fn
The same name coverage problem,thisArg
On the object__fn
That’s overwritten and then deleted.
Solution 1 to Problem 1: Use ES6 Sybmol() unique. It could be the way to simulate ES3. What if the interviewer doesn’t allow it. Solution 2: Implement the unique key yourself using the math.random () simulation. When interviewing, you can use the generated timestamp directly.
// Generate the universal unique identifier (UUID) // generate a string like this'18efca2d-6e25-42bf-a636-30b8f9f2de09'
function generateUUID(){
var i, random;
var uuid = ' ';
for (i = 0; i < 32; i++) {
random = Math.random() * 16 | 0;
if (i === 8 || i === 12 || i === 16 || i === 20) {
uuid += The '-';
}
uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random))
.toString(16);
}
returnuuid; } // Simple implementation //'__' + new Date().getTime();
Copy the code
If the key is still in the object, you can do a cache operation to be on the safe side. For example:
var student = {
name: 'if the sichuan'.doSth: 'doSth'}; var originalVal = student.doSth; var hasOriginalVal = student.hasOwnProperty('doSth');
student.doSth = function() {}; delete student.doSth; // If 'originalVal' does not exist, then 'originalVal' is undefined.if(hasOriginalVal){
student.doSth = originalVal;
}
console.log('student:', student); // { name: 'if the sichuan'.doSth: 'doSth' }
Copy the code
- 2. Use
ES6
Extended operator.
Solution 1: Adopteval
To execute the function.
Eval parses strings into code to execute. MDN documents: eval syntax
eval(string)
Copy the code
The string argument represents a JavaScript expression, statement, or string of statements. Expressions can contain variables as well as properties of existing objects. Return Value The return value after the specified code has been executed. If the return value is null, return undefined. Solution 2: But what if the interviewer doesn’t allow eval, because eval is the devil. You can use new Function() to generate the execution Function. MDN documentation: Function syntax
new Function ([arg1[, arg2[, ...argN]],] functionBody)
Copy the code
The arguments arg1, arg2… ArgN The name of the argument used by the function must be legally named. The parameter name is either a string of valid JavaScript identifiers, or a comma-separated list of valid strings; For example, “×”, “theValue”, or “A, B”. FunctionBody A string containing a JavaScript statement that contains the function definition. Here are two examples:
Var sum = new Function()'a'.'b'.'return a + b');
console.log(sum(2, 6));
Copy the code
Var student = {name:'if the sichuan'.doSth: function(argsArray){ console.log(argsArray); console.log(this.name); }}; // var result = student.doSth(['Rowboat'18]); Var result = new Function() var result = new Function()'return arguments[0][arguments[1]](arguments[2][0], arguments[2][1])')(student, 'doSth'['Rowboat'18]); // So we can write a function to generate function code:function generateFunctionCode(argsArrayLength){
var code = 'return arguments[0][arguments[1]](';
for(var i = 0; i < argsArrayLength; i++){
if(i > 0){
code += ', ';
}
code += 'arguments[2][' + i + '] ';
}
code += ') ';
// return arguments[0][arguments[1]](arg1, arg2, arg3...)
return code;
}
Copy the code
You probably don’t know aboutES3, ES5
In theundefined
It can be modified
Probably most people don’t. In ES5, although the global scope can not be modified, but in the local scope can also be modified, do not believe to copy the following test code in the console execution. Although you don’t normally modify it.
function test(){ var undefined = 3; console.log(undefined); // Chrome is also 3}test(a);Copy the code
Typeof a === ‘undefined’ or a === void 0; We’re using void here, and void evaluates expressions, always returns undefined, or we can write void(0). For more information, see this article by Han Zichi: Why using “void 0” instead of “undefined” solves these problems and makes it easier to implement the following code.
usenew Function()
Simulated implementedapply
// The browser environment is not strict modefunction getGlobalObject() {return this;
}
function generateFunctionCode(argsArrayLength){
var code = 'return arguments[0][arguments[1]](';
for(var i = 0; i < argsArrayLength; i++){
if(i > 0){
code += ', ';
}
code += 'arguments[2][' + i + '] ';
}
code += ') ';
// return arguments[0][arguments[1]](arg1, arg2, arg3...)
return code;
}
Function.prototype.applyFn = functionApply (thisArg, argsArray){// The 'length' attribute of the 'apply' method is' 2 '. // 1. If 'IsCallable(func)' is'false, a TypeError exception is raised.if(typeof this ! = ='function'){
throw new TypeError(this + ' is not a function'); } // 2. If argArray is null or undefined, then // returns the result of calling func's [[Call]] internal method with an empty argument list and providing thisArg as this value.if(typeof argsArray === 'undefined'|| argsArray === null){ argsArray = []; } // 3. If Type(argArray) is not Object, raise TypeError.if(argsArray ! == new Object(argsArray)){ throw new TypeError('CreateListFromArrayLike called on non-object');
}
if(typeof thisArg === 'undefined'| | thisArg = = = null) {/ / incoming outside thisArg value will change and become this value. // ES3: when thisArg is undefined or null, it will be replaced by window thisArg = getGlobalObject(); } // ES3: All other values are applied ToObject and the result is this, a change introduced in version 3. thisArg = new Object(thisArg); var __fn ='__'+ new Date().getTime(); Var originalVal = thisArg[__fn]; var originalVal = thisArg[__fn]; Var hasOriginalVal = thisarg. hasOwnProperty(__fn); thisArg[__fn] = this; // 9. Provide 'thisArg' as the 'this' value and' argList 'as the argument list, and Call the' [[Call]] 'internal method of' func ', returning the result. // var result = thisArg[__fn](... args); var code = generateFunctionCode(argsArray.length); var result = (new Function(code))(thisArg, __fn, argsArray); delete thisArg[__fn];if(hasOriginalVal){
thisArg[__fn] = originalVal;
}
return result;
};
Copy the code
Using simulationapply
Analog implementationcall
Function.prototype.callFn = function call(thisArg){
var argsArray = [];
var argumentsLength = arguments.length;
for(var i = 0; i < argumentsLength - 1; i++){
// argsArray.push(arguments[i + 1]);
argsArray[i] = arguments[i + 1];
}
console.log('argsArray:', argsArray);
returnthis.applyFn(thisArg, argsArray); } // Test the example vardoSth = function (name, age){
var type = Object.prototype.toString.call(this);
console.log(typeof doSth);
console.log(this === firstArg);
console.log('type:'.type);
console.log('this:', this);
console.log('args:', [name, age], arguments);
return 'this--';
};
var name = 'window';
var student = {
name: 'if the sichuan',
age: 18,
doSth: 'doSth',
__fn: 'doSth'}; var firstArg = student; var result =doSth.applyFn(firstArg, [1, {name: 'Rowboat'}]);
var result2 = doSth.callFn(firstArg, 1, {name: 'Rowboat'});
console.log('result:', result);
console.log('result2:', result2);
Copy the code
Argsarray. push(arguments[I + 1]); In fact, the push method also has a layer of loops inside. So in theory it would be better not to use push. The interviewer may also use this to ask questions about time and space complexity.
// Look at the implementation in V8:function ArrayPush() { var n = TO_UINT32( this.length ); Var m = %_ArgumentsLength(); // Number of parameters for pushfor(var i = 0; i < m; i++) { this[ i + n ] = %_Arguments( i ); // Copy element (1)} this.length = n + m; // Fix the length attribute value (2)return this.length;
};
Copy the code
This is basically the end of the line, but you may also notice that in the non-strict mode of writing, thisArg raw value is wrapped into an object, added and executed, and then deleted. In strict mode, this is not implemented, and in case the Object is a frozen Object, object.freeze ({}) cannot add properties to the Object. Therefore, this method can only be regarded as a simple implementation in the non-strict mode. So just to sum up.
conclusion
Understand call and apply through MDN, read ES5 specification, implement APPLY through simulation, and then implement Call. This is thisArg. This is thisArg. This is thisArg. This is thisArg. Leads to the ES6 Symbol, the extension of ES6… , eval, new Function(), strict mode, etc. In fact, a real business scenario does not need to simulate the implementation of Call and Apply, which is what ES3 provides. But there are a lot of basic things the interviewer can learn about the candidate. For example, the use of call, apply. ES6 Symbol, ES6 Symbol… , eval, new Function(), strict mode, and even time and space complexity. Readers are welcome to point out any shortcomings or improvements they find. In addition, I think it is well written, and I can point a like, which is also a kind of support for the author.
// Delete the final version of the annotated version, detailed comments to see the article // browser environment is not strict modefunction getGlobalObject() {return this;
}
function generateFunctionCode(argsArrayLength){
var code = 'return arguments[0][arguments[1]](';
for(var i = 0; i < argsArrayLength; i++){
if(i > 0){
code += ', ';
}
code += 'arguments[2][' + i + '] ';
}
code += ') ';
return code;
}
Function.prototype.applyFn = function apply(thisArg, argsArray){
if(typeof this ! = ='function'){
throw new TypeError(this + ' is not a function');
}
if(typeof argsArray === 'undefined' || argsArray === null){
argsArray = [];
}
if(argsArray ! == new Object(argsArray)){ throw new TypeError('CreateListFromArrayLike called on non-object');
}
if(typeof thisArg === 'undefined' || thisArg === null){
thisArg = getGlobalObject();
}
thisArg = new Object(thisArg);
var __fn = '__' + new Date().getTime();
var originalVal = thisArg[__fn];
var hasOriginalVal = thisArg.hasOwnProperty(__fn);
thisArg[__fn] = this;
var code = generateFunctionCode(argsArray.length);
var result = (new Function(code))(thisArg, __fn, argsArray);
delete thisArg[__fn];
if(hasOriginalVal){
thisArg[__fn] = originalVal;
}
return result;
};
Function.prototype.callFn = function call(thisArg){
var argsArray = [];
var argumentsLength = arguments.length;
for(var i = 0; i < argumentsLength - 1; i++){
argsArray[i] = arguments[i + 1];
}
return this.applyFn(thisArg, argsArray);
}
Copy the code
Further reading
JavaScript Design Patterns and Development Practices – Chapter 2 ES5: call/bind/bind/bind/bind/bind/bind
Selected articles by the author
Learn how to use sentry source code and build their own front-end exception monitoring SDK. Learn how to use lodash source code and build their own functional programming library. Q: Can you simulate the js call and apply methods? Q: Can you simulate the js bind method? Q: Can you simulate the js bind method? Can simulate the implementation of the JS new operator front end using puppeteer crawler to generate the React. JS small book PDF and merge
about
Author: often with the name of Ruochuan mixed in rivers and lakes. The front road lovers | | PPT know little, only good study. Personal blog segmentfault front view column, opened front view column, welcome to pay attention to ~ gold column, welcome to pay attention to ~ github front view column, welcome to pay attention to ~ github blog, find a star^_^~
Wechat public account Ruochuan Vision
May be more interesting wechat public number, long press scan code concern. You can also add wechat Ruochuan12, indicate the source, pull you into [front view communication group].