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.