Understanding meta-property - newTarget

Digging deep into newTarget - `new.target` from ECMAScript 2015 (aka ES6)


9 minutes read


new.target is one of newly introduced meta-properties that have made it to ECMAScript/JavaScript. It is also known as newTarget in official ES2015 specifications. It lets you detect whether a function (constructor function) was called with new keyword or not.


Following section shall shed some more light on the newly available meta-property.

Table of contents


What is new.target?

The new.target property lets you find if, a function or constructor was called using the new operator and also helps to find the called constructor. In constructors and functions instantiated with the new operator, new.target returns a reference to the constructor or function. In normal function calls, i.e without new operator, new.target is undefined.

new.target as per specification

newTarget is meta-property and is not a function. Its value is the function, if its called with new keyword else it is undefined. In official specification, it is referred as ‘Runtime Semantics’ and returns GetNewTarget().


Primary points

Before going into usage of newTarget, let us consider following few points,

Constructor functions/methods

You can invoke a constructor function via the new operator, then only it becomes a constructor, a factory for objects. By convention, the names of constructors start with uppercase letters. A example implementation of Constructor function. We shall use this in further discussion.

//constructor function
function Animal(type) {
    this.type = type;
}
Animal.prototype.getType = function () {
    return this.type;
};

//Usage
var cat = new Animal("Cat");

Here we have a ‘Animal’ constructor, which takes ‘type’ of animal as its argument and creates an instance of Animal with that type. Here we have ‘cat’ instance whose type is ‘Cat’ (cat.type).


Problem with this

If you forget to call constructor, Animal, without the keyword new, you will end up referring this as parent object (typically, window object) and eventually polluting the global scope/environment.

Consider this snippet,

//constructor function
function Animal(type) {
    this.type = type;
}
Animal.prototype.getType = function () {
    return this.type;
};

//Usage
var cat = Animal("Cat"); //no error thrown, no warning!

// cat is not any instance.
console.log(cat); //undefined
// global variable is created, window.type -> 'Cat'
console.log(type); // Cat

This is also known as sloppy mode. Here, the constructor function always has thisArg. We won’t be digging into how new operator works but typically, when new operator is used, this points to a new Object; else it points to global object (window).


Typical Solution - Using strict mode, ‘use strict’

In strict mode, an exception is thrown when constructor function is not called with new. Consider,

//constructor function
function Animal(type) {
    "use strict";
    this.type = type;
}
Animal.prototype.getType = function () {
    return this.type;
};

//Usage
var cat = Animal("Cat"); //TypeError: Cannot set property 'type' of undefined!

In strict mode, if you do not call constructor function with new keyword, thisArg is undefined. Which means, one cannot set property ‘type’ for a undefined type. Hence, ‘TypeError’ is thrown in case of strict mode.


Use Cases

Apart from over coming problem of global scope change, ie, this referring to window object; constructor function needs to find whether it was called with new operator for following reasons,

  1. Force usage of function as a constructor only, which implies raising exception when function is not used as constructor.
  2. Provide dual functionality for constructor factory. For eg, wrapper objects for primitive, like Number() act as constructor when used with ‘new’ and acts as a function that converts values to primitive type for numbers.
> typeof new Number(123)
'object'
> new Number(123) === 123
false
> Number(123)
123
> Number('abc')
NaN

Case 1 solution:

//Strict constructor function
function Animal(type) {
    "use strict";
    if (this === undefined) {
        throw new Error("Animal() must be called with new.");
    }
    this.type = type;
}

Case 2 solution:

//Wrapper Objects
function WrapperConstructor(type) {
'use strict'
if(this === undefined){
// WrapperConstructor is called without new
// Act as factory for sub-objects or conversion function
...
}
// Else this is defined and used with new operator
// Act as Constructor or factory of WrapperConstructor
...
}

Introducing newTarget

new.target is an implicit parameter that all functions have. It is to constructor calls what this is to method calls. As mentioned, if used with new operator, it returns the constructor function else it is undefined.

Here as typical solutions to use cases that we discussed earlier

Case 1 solution: (using newTarget)

function Animal(type) {
    if (!new.target) {
        throw new Error("Animal() must be called with new.");
    }
    this.type = type;
}

Case 2 solution:

//Wrapper Objects
function WrapperConstructor(type) {

    if(!new.target){
     // WrapperConstructor is called without new
     // Act as factory for sub-objects or conversion function
     ...
    }
    // Else this is defined and used with new operator
    // Act as Constructor or factory of WrapperConstructor
    ...

}

Is newTarget just a syntactical sugar?

Simply, no because

  • Its sole purpose is to retrieve the current value of the [[NewTarget]] value of the current (non-arrow) function environment. It is a value that is set when a function is called (very much like the this binding).
  • If this Environment Record was created by the [[Construct]] internal method, [[NewTarget]] is the value of the [[Construct]] newTarget parameter. Otherwise, its value is undefined.
  • So, for one thing, finally enables us to detect whether a function was called as a constructor or not.

But that’s not its actual purpose.It is part of the way how ES6 classes are not only syntactic sugar, and how they allow us inheriting from builtin objects. When you call a class constructor via new X, the this value is not yet initialized - the object is not yet created when the constructor body is entered. It does get created by the super constructor during the super() call (which is necessary when internal slots are supposed to be created). Still, the instance should inherit from the .prototype of the originally called constructor, and that’s where newTarget comes into the play. It does hold the “outermost” constructor that received the new call during super() invocations. You can follow it all the way down in the spec, but basically it always is the newTarget not the currently executed constructor that does get passed into the new.target.

function Parent() {
    console.log("Value of newTarget in Parent", new.target); //undefined
    console.log("Is Target Parent?", new.target === Parent); //false
}

function Child() {
    console.log("Value of newTarget in Child", new.target); //function Child
    Parent.apply(this, arguments);
    console.log("Is Target Child?", new.target === Child); //true
}

Child.prototype = Object.create(Parent);
Child.prototype.constructor = Child;

var child = new Child();
  • Consider the case for ES6 Classes

class Parent { constructor() { // implicit (from the super call) // new.target = Child; // implicit (because Parent doesn’t extend anything): // this = Object.create(new.target.prototype); console.log(new.target) // Child! } } class Child extends Parent { constructor() { // implicit (from the new call): // new.target = Child super(); console.log(this); } } let child = new Child();

Following extract for classes would explain `newTarget` in classes

```javascript
class A {
constructor() {
console.log(new.target.name);
}
}

class B extends A { constructor() { super(); } }

var a = new A(); // logs "A"
var b = new B(); // logs "B"

class C { constructor() { console.log(new.target); } }
class D extends C { constructor() { super(); } }

var c = new C(); // logs class C{constructor(){console.log(new.target);}}
var d = new D(); // logs class D extends C{constructor(){super();}}

Thus from the above example of class C and D, it seems that new.target points to the class Definition of class which is initialized. For example, when D was initialized using new, the class definition of D was printed and similarly in case of c, class C was printed.


Lexical new.target in arrow functions ()=>{}

new.target is lexical in arrow functions, just like arguments and this are lexical, i.e., value of new.target is taken from surrounding environment.


Polyfill

This is not polyfill-able property, as it is token/expression and uses property from callee. Also, Polyfill-ing doesn’t makes any sense here because it not a function to be called. Even if treated as property, new is a keyword/operator and not a object that could hold target as its property.


Browser Support

Chrome Edge Firefox Internet Explorer Opera Safari
46 Yes 41 NO Yes yes

References

For more detail, refer meeting notes from ES discussion on newTarget