Talk deeply about the principle and implementation of new in JS

  • 2021-12-04 09:29:51
  • OfStack

Directory definition constructor body is different No return value
Return object
Returns a non-object
No property binding + return non-object
Constructor types are different Constructor is a normal function
The constructor is an arrow function
Handwritten new Summarize

Definition

The new operator creates an instance of a user-defined object type or of a built-in object with a constructor.

Use new [constructor] to create 1 object instance, but differences in constructors cause different instances to be created.

Constructor body is different

Constructors are also functions, and the only difference between them is that they are called in different ways. Any function called by new operator is a constructor, while a function called without new operator is an ordinary function.

So the constructor can also have a return value, but this will make the result of new different.

No return value


function Person(name) {
  this.name = name;
}

let obj = new Person("Jalenl");
console.log(obj);

Obviously, you're printing {name: 'Jalenl'}

Return object


function Person(age) {
  this.age = age;
  return { name: "Jalenl" };
}

let obj = new Person(18);
console.log(obj);

You print {name: 'Jalenl'}, which means that the definitions before return are overwritten. Here, return is an object, so what about returning a basic type?

Returns a non-object


function Person(age) {
  this.age = age;
  return 1;
}

let obj = new Person(18);
console.log(obj);

Returns {age: 21}, so return is invalid, which is the same as the result without return1. What if there is no this binding internal attributes and then returning the basic data type?

No property binding + return non-object


function Person(){
    return 1
}
new Person()

Returned 1 empty object {}, as expected.

To sum up, the initial result can only be changed if the constructor return returns 1 object type.

Constructor types are different

Constructor is a normal function

ECMA-262 3rd. The object instance creation process is described in Edition Specification:

13.2.2 [[Construct]]
When the [[Construct]] property for a Function object F is called, the following steps are taken:

Create a new native ECMAScript object. Set the [[Class]] property of Result(1) to "Object". Get the value of the prototype property of F. If Result(3) is an object, set the [[Prototype]] property of Result(1) to Result(3). If Result(3) is not an object, set the [[Prototype]] property of Result(1) to the original Object prototype object as described in 15.2.3.1. Invoke the [[Call]] property of F, providing Result(1) as the this value and providing the argument list passed into [[Construct]] as the argument values. If Type(Result(6)) is Object then return Result(6). Return Result(1).

To sum up, it is:

Creates 1 new object in memory. The [[Prototype]] attribute inside this new object is assigned to the constructor's prototype attribute. this inside the constructor is assigned to this new object (that is, this points to the new object). Executes the code inside the constructor (adding attributes to the new object). If the constructor returns an object, the object is returned; Otherwise, the new object (empty object) just created is returned.

The reason why new results differently due to different constructors is explained in Step 5.

The following is excerpted from the explanation of MDN:

When the code new Foo (…) executes, the following things happen:

A new object inherited from Foo. prototype was created. Invokes the constructor Foo with the specified parameters and binds this to the newly created object. new Foo is equivalent to new Foo (), that is, Foo is called without any parameters without specifying a parameter list. The object returned by the constructor is the result of the new expression. If the constructor does not explicitly return 1 object, the object created in Step 1 is used. (1 Typically, the constructor does not return a value, but the user can choose to actively return the object to override the normal object creation step.)

The constructor is an arrow function

When a normal function is created, the engine creates an prototype property (pointing to the prototype object) for the function according to specific rules. By default, all prototype objects automatically get a property named constructor, referring back to the constructor associated with it.


function Person(){
    this.age = 18;
}
Person.prototype
/**
{
    constructor: ƒ Foo()
    __proto__: Object
}
**/

When an arrow function is created, the engine does not create an prototype attribute for it, and the arrow function does not have an constructor for new to call, so calling the arrow function with new will report an error!


const Person = ()=>{}
new Person()//TypeError: Foo is not a constructor

Handwritten new

To sum up, after being familiar with the working principle of new, we can implement a low-profile version of new by ourselves. The key to implementation is:

Give instances access to private properties; Give instances access to properties on the prototype chain where the constructor prototype (constructor. prototype) resides; The final result returned by the constructor is the reference data type.

function _new(constructor, ...args) {
    //  Legal Judgment of Constructor Type 
    if(typeof constructor !== 'function') {
      throw new Error('constructor must be a function');
    }
    //  New Empty Object Instance 
    let obj = new Object();
    //  Bind the prototype of the constructor to the newly created object instance 
    obj.__proto__ = Object.create(constructor.prototype);
    //  Call the constructor and judge the return value 
    let res = constructor.apply(obj,  args);
    let isObject = typeof res === 'object' && res !== null;
    let isFunction = typeof res === 'function';
    //  If there is a return value and the return value is of object type, it is taken as the return value, otherwise, the newly created object is returned 
    return isObject || isFunction ? res : obj;
};

This low-level new implementation can be used to create instances of custom classes, but it does not support built-in objects. After all, new is an operator, and the underlying implementation is more complex.

Summarize


Related articles: