JavaScript often uses constructors to create objects (calling a function through the new operator), so what happens when you call a function using new? Let’s look at a few examples and explain what’s going on behind the scenes.

1) Look at three examples

1.1 No Return Statement

The constructor does not end with a return statement, which is the default when constructors are used. A new object is returned as follows:

function Foo(age) {
  this.age = age;
}

var o = new Foo(111);
console.log(o);
Copy the code

This is a common procedure for creating an object using the constructor, which prints {age: 111}.

1.2 Return Object type data

The constructor returns the object type data:

function Foo(age) {
  this.age = age;

  return { type: "I'm returning it explicitly." };
}

var o = new Foo(222);
console.log(o);
Copy the code

{type: ‘I explicitly return ‘}, that is, all the work before return is wasted, and the object after return is returned.

1.3 Return Basic Type Data

If there is a return at the end of the constructor, it will return the data after the return.

Let’s look at the case where the basic type is returned:

function Foo(age) {
  this.age = age;

  return 1;
}

var o = new Foo(333);
console.log(o);
Copy the code

It prints {age: 333}, just as it would without a return. As expected, behind the principle of you see the following analysis.

2) The principle behind it

2.1 The case of non-arrow functions

When creating objects using the new operator, the official ES5 documentation gives the following definition in the function Definitions section 13.2.2 [[Construct]] :

When the [[Construct]] internal method for a Function object F is called with a possibly empty list of arguments, the following steps are taken:

  1. Let obj be a newly created native ECMAScript object.
  2. Set all the internal methods of obj as specified in 8.12.
  3. Set the [[Class]] internal property of obj to Object.
  4. Set the [[Extensible]] internal property of obj to true.
  5. Let proto be the value of calling the [[Get]] internal property of F with argument “prototype”.
  6. If Type(proto) is Object, set the [[Prototype]] internal property of obj to proto.
  7. If Type(proto) is not Object, set the [[Prototype]] internal property of obj to the standard built-in Object prototype object as described in 15.2.4.
  8. Let result be the result of calling the [[Call]] internal property of F, providing obj as the this value and providing the argument list passed into [[Construct]] as args.
  9. If Type(result) is Object then return result.
  10. Return obj.

Look at steps 8 and 9:

8) Call F and assign its return value to result; Where F is executed with arguments passed to [[Construct]] (F itself). Inside F, this points to obj. 9) Return result if result is of type Object;

This explains why if a constructor explicitly returns an object type, it returns that object directly, rather than the object originally created.

Finally, look at Step 10:

10) If F returns a non-object type (step 9 does not hold), return the created object obj.

If the constructor does not explicitly return the object type (either explicitly or not directly), the object that was originally created is returned.

2.2 The case of arrow functions

What if the constructor is an arrow function?

The arrow function does not have the [[Construct]] method and cannot be called with new.

NOTICE: [[Construct]] is the constructor itself.

The relevant specification is mentioned in the official ES6 documentation, but the official documentation since ES6 is so difficult that it will not be described here.

3) New calls the complete process of the function

3.1 Chinese description and relevant code analysis

Any function other than the arrow function can be called using new.

1) Create ECMAScript native object obj; 2) Set the internal properties of the native object to obj; (unlike the stereotype property, the inner property is represented as [[PropertyName]], with two square brackets wrapped around the PropertyName and capitalized, [[Constructor]]) 3) set [[Class]] to Object; 4) Set the internal properties of obj [[Extensible]] to true; 5) Set proto to the prototype property of F; 6) If proto is an object type, set obj’s internal property [[Prototype]] to proto; If proto is not an Object type, set the internal property [[Prototype]] of obj to the Prototype value of the built-in constructor Object. The prototype property of the function can be overwritten. If set to a non-object type, obj’s [[prototype]] refers to the prototype Object. (Decide what to return)

For the case of step 7, see the following code:

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

var o1 = new Foo("xiaoming");
console.log(o1.__proto__ === Foo.prototype); // true

The [[Prototype]] property inside the instance points to the Object Prototype Object
// Since an instance is an object type of data, it inherits the stereotype of the built-in object by default,
// If the stereotype of the constructor does not meet the requirements of the stereotype chain, then skip the association directly with the built-in object stereotype
Foo.prototype = 1;
var o2 = new Foo("xiaohong");
console.log(o2.__proto__ === Foo.prototype); // false
console.log(o2.__proto__ === Object.prototype); // true
Copy the code

3.2 More concise language description

If new Foo() is executed, the procedure is as follows:

1) create object o; [[Prototype]]; [[Prototype]]; [[Prototype]]; Otherwise point to the Object’s prototype); 3) Execute function Foo with internal this pointing to newly created object O; 4) If Foo internally explicitly returns object type data, return that data, and the execution is complete; Otherwise return the newly created object O.

4) A few notes

4.1 Checking whether it is Object

The instanceof operator can be used to determine whether a piece of data is of type Object: if x instanceof Object returns true, x is of type Object.

Null instanceof Object returns false, so null is not of type Object, even though typeof NULL returns “Object”.

4.2 instanceof principle

Instanceof works like this: in the expression x instanceof Foo, it returns true if Foo’s prototype (foo.prototype) appears in x’s prototype chain, and false otherwise.

Because the prototype of a function can be overwritten, it will be the case that x instanceof Foo will return false after it has been completely overwritten after x comes out of Foo new. Because the constructor prototype is overridden after the instance is created, the prototype that the instance points to is no longer the new prototype of the constructor, as shown in the following code:

const Foo = function() {};

const o = new Foo();

o instanceof Foo; // true

// Rewrite the Foo prototype
Foo.prototype = {};
o instanceof Foo; // false
Copy the code

The resources

What values can a constructor return to avoid returning this?

[[Construct]] internal method