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:

  1. First of all toFnthisMemberExpressionThe result is a reference to the actual function object, which we refer to asref
  2. Then callGetValue(ref)Evaluate it, get the actual function object, and take that object asconstructor
  3. rightArgumentsThat is, the parameters that are passed in are evaluated, and you get a list of parameters asargList
  4. ifconstructorIf it is not an object, a type error is thrown
  5. ifconstructorInternal is not implemented[[Constructor]]Method, also throws a type error
  6. callconstructor[[Constructor]]Method, and willargListPassed 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:

  1. letobjAs a newly created native object
  2. As specified by the specification, isobjSet all internal methods
  3. willobjInternal properties of[[Class]]Set toObject
  4. The ginsengprototypeCall a functionFInternal methods of[[Get]], gets the function’s prototype object asproto
  5. ifprotoIs an object, thenobjInternal properties of[[Prototype]]Set toproto
  6. ifprotoIs not an objectobjInternal properties of[[Prototype]]Set to standard built-inObjectThe prototype object of
  7. Call a functionFInternal methods ofCall.objAs 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
  8. ifresultIs an object, it is returned
  9. Otherwise, returnobj

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 theObjectprototype
  • 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