This is the third day of my participation in Gwen Challenge
This is the third article in a series exploring the principles of JS native methods. This article shows you how to simulate implementing the new operator. As for the specific use of new, MDN has been described very clearly, here we will not talk about nonsense, directly how to simulate the implementation.
Specification of the new operator
Note: All of the specifications shown below are ES5 versions and differ somewhat from the current specification
Let’s first look at what the new operator does according to the specification:
It’s all in English, but it doesn’t matter, I’ll translate it briefly:
When I use the new operator, the constructor may or may not take arguments. If it does not, such as new Fn(), then Fn is a NewExpression; If it takes an argument, such as new Fn(name,age), then Fn is a MemberExpression.
The operation with the new operator is slightly different in these two cases, as illustrated here with arguments:
- First of all to
Fn
thisMemberExpression
The result is a reference to the actual function object, which we refer to asref
- Then call
GetValue(ref)
Evaluate it, get the actual function object, and take that object asconstructor
- right
Arguments
That is, the parameters that are passed in are evaluated, and you get a list of parameters asargList
- if
constructor
If it is not an object, a type error is thrown - if
constructor
Internal is not implemented[[Constructor]]
Method, also throws a type error - call
constructor
的[[Constructor]]
Method, and willargList
Passed as an argument, returns the result of the call
As you can see from these descriptions, more implementation details are in the function’s [[Constructor]] method. So what exactly does this method do?
[[Constructor]]
The specification of the
In JS, functions can be called either normally, which calls the internal method of the function [[Call]], or via new, which calls another internal method of the function [[Consturct]]. So, to implement the new operation, we need to understand what the [[Construct]] inner method is doing.
Here’s what the spec says:
A brief translation:
When the internal method [[Construct]] of function F is called with a possibly empty argument list, the following steps are performed:
- let
obj
As a newly created native object - As specified by the specification, is
obj
Set all internal methods - will
obj
Internal properties of[[Class]]
Set toObject
- The ginseng
prototype
Call a functionF
Internal methods of[[Get]]
, gets the function’s prototype object asproto
- if
proto
Is an object, thenobj
Internal properties of[[Prototype]]
Set toproto
- if
proto
Is not an objectobj
Internal properties of[[Prototype]]
Set to standard built-inObject
The prototype object of - Call a function
F
Internal methods ofCall
.obj
As the this value at the time of the call, previously passed[[Construct]]
The argument list for the call. Take the result of the call asresult
- if
result
Is an object, it is returned - Otherwise, return
obj
In a nutshell, when a constructor is new, it does exactly the following:
- Internally create an instance object and specify the prototype of the instance object:
- If the constructor’s prototype is an object, let the instance’s
__proto__
Is equal to the constructorprototype
- If the constructor’s prototype is not an object, the instance’s
__proto__
Is equal to theObject
的prototype
- If the constructor’s prototype is an object, let the instance’s
- Bind the instance object to this in the constructor, with the previously passed argument as the argument, and execute the constructor once
- If the constructor returns an object, it is the return value, otherwise the instance object is the return value
Code implementation
The ES3 version is implemented as follows:
function myNew(Fn){
if(typeofFn ! ='function') {throw new TypeError(Fn + 'is not a constructor')
}
myNew.target = Fn
var instance = {}
// Check whether the constructor prototype is an object
instance.__proto__ = Fn.prototype instanceof Object ? Fn.prototype : Object.prototype
const returnValue = Fn.apply(instance,Array.prototype.slice.call(arguments.1))
if(typeof returnValue === 'object'&& returnValue ! = =null || typeof returnValue === 'function') {return returnValue
} else {
return instance
}
}
Copy the code
The implementation of ES6 is as follows:
function myNew(Fn,... args){
if(typeofFn ! ='function') {throw new TypeError(Fn + 'is not a constructor')
}
myNew.target = Fn
const instance = {}
// Check whether the constructor prototype is an object
instance.__proto__ = Fn.prototype instanceof Object ? Fn.prototype : Object.prototype
constreturnValue = Fn.call(instance,... args)return returnValue instanceof Object ? returnValue : instance
}
Copy the code
A few points to note:
1) When a function is called from new, new.target refers to the function itself. Mynew.target = Fn refers to the function itself
Const instance = object.create (Fn. Prototype); If it is not an Object, say null, then the __proto__ of the instance is relinked to Object.prototype. Causes the instance’s __proto__ to still point to null. Many mock implementations of new on the web use object.create directly, or don’t type check the constructor prototype at all, which is not rigorous enough (note that I’m not saying this is wrong, just not close enough to the implementation details of native [[Consturct]]). You can read another article I wrote earlier.
Prototype === ‘Object’ &&fn. Prototype! == null