42

The ECMAScript 2015 specification mention the keyword (or words?) new.target exactly 3 times - 1 time in 14.2.3:

Normally, Contains does not look inside most function forms However, Contains is used to detect new.target, this, and super usage within an ArrowFunction.

and twice in 14.2.16:

An ArrowFunction does not define local bindings for arguments, super, this, or new.target. Any reference to arguments, super, this, or new.target within an ArrowFunction must resolve to a binding in a lexically enclosing environment

MDN mentions it, but is very vague and the page is incomplete.

Babel doesn't seem to support it. I got syntax errors when trying to use new.target in a function (arrow or others).

What is it, and how is it supposed to be used?

3 Answers 3

59

You didn't find it in the spec because in the syntax definitions it is written with blanks, as new . target. The name of the expression is NewTarget, and you'll find that term a few times around.

NewTarget is the first of the so-called meta properties and can be found in §12.3.8.

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), and according to §8.1.1.3 Function Environment Records:

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 real purpose. So what is it then? 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 initialised - 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 OrdinaryCreateFromConstructor procedure - for example in step 5 of §9.2.2 [[Construct]] for user-defined functions.

Long text, maybe an example is better suited:

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() {
        // `this` is uninitialised (and would throw if accessed)
        // implicit (from the `new` call):
        //    new.target = Child 
        super(); // this = Reflect.construct(Parent, [], new.target);
        console.log(this);
    }
}
new Child;
Sign up to request clarification or add additional context in comments.

13 Comments

So that means its valid syntax to write console.log(new . target) (with spaces) inside a function?
@Amit: I presume - it's multiple tokens, which can always be separated via whitespace. It's valid ES to write console . log(…) as well :-) Of course no one does it - except for line breaks when method chaining…
but that not quite the same. console is an identifier, new is a keyword. That would make it quite odd when considering new Obj() is valid syntax.
@Asad: You cannot access this before the super() call - when you try referencing it, it throws a ReferenceError.
@Maximus Not a child, but the Child constructor itself. And we want the new instance to inherit from the Child.prototype object
|
11

It's primarily intended as a better way to detect when a constructor is called without new.

From http://www.2ality.com/2015/02/es6-classes-final.html:

new.target is an implicit parameter that all functions have. It is to constructor calls what this is to method calls.

So I can write:

function Foo() {
  if (!new.target) throw "Foo() must be called with new";
  ...
}

There are more details to how this works, and more contexts in which it is useful, but we'll leave it here.

For some meeting notes regarding new.target, see https://esdiscuss.org/notes/2015-01-27.

11 Comments

I think it is impossible to call a constructor without new (you'll get an exception). Unless by "constructor" you mean a regular function called with "new", and I'm just misunderstanding you.
@Asad A constructor is a regular function called with new.
I see. I was thinking of it in the sense of the new ES6 constructor-keyword constructors, which TypeError if you don't invoke them with new.
Where is any of this defined in the spec?
@Amit I'm talking about constructors in the classic sense of function Foo().
|
4

What is new.target?

new.target is the constructor of the current object this.

Why would I use new.target if there is this.constructor? What's the difference?

The difference between new.target and this.constructor is that new.target is the future constructor of the object, while this.constructor is a reference to the object's constructor used in the past to construct the object.

How is new.target supposed to be used?

It is supposed to be used whenever this.constructor cannot be.

Realistic use case for new.target in a TypeScript project

Suppose, the program defines classes Work (some abstract effort), Algorithm (certain specific kind of effort) and SortingAlgorithm (specific kind of algorithm):

class Work {}

class Algorithm extends Work {}

class SortingAlgorithm extends Algorithm {}

Now let's define a name. Work can't have a name, but algorithms (and certainly some well-known sorting algorithms) – can. So, we define name on an instance of Algorithm or any derived class (i.e., on SortingAlgorithm), but not on Work:

class Algorithm extends Work {
  constructor(public name: string) {
    super()
  }
}

Try it.

From now on, every single Algorithm instance must specify name. But that's inconvenient, because while certain well-known algorithms do have names (e.g., A*, or Bubble sort), custom business-specific algorithms (i.e., getting a user from the database by ID) might have names but they don't have to. So, the name input should be optional.

We don't want nondescript or undefined names. So, in case if name is not provided, we choose to name it after the constructor (i.e., "Algorithm" for unnamed algorithms, and "SortingAlgorithm" for unnamed sorting algorithms). To avoid repetition, we'll try to take the constructor name from this.constructor; let's use a default parameter syntax for that:

class Algorithm extends Work {
  constructor(public name = this.constructor.name) {
    //                      ^^^^
    // Error: 'super' must be called before accessing 'this' in the constructor of a derived class.
    super()
  }
}

Try it.

… which we can't do. In derived classes, this is only available after calling super(), so we can't use this anywhere before the body of the constructor, where super() is actually called.

We can walk around that problem by opting out of the (very convenient) Parameter properties syntax, and/or using nullish-coalescing operator, but that ignores the weirdness:

We are trying to infer the constructor, a reference to which we already have, from the instance, which hadn't even been created yet.

This is exactly the case where new.target shines: it is a reference to the constructor of the future instance, available in the constructor method even before actually creating the instance:

class Algorithm extends Work {
  constructor(public name = new.target.name) {
    super()
  }
}

Try it.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.