Using new.target in Class Constructors

In Chapter 3, you learned about new.target and how its value changes depending on how a function is called. You can also use new.target in class constructors to determine how the class is being invoked. In the simple case, new.target is equal to the constructor function for the class, as in this example:

  1. class Rectangle {
  2. constructor(length, width) {
  3. console.log(new.target === Rectangle);
  4. this.length = length;
  5. this.width = width;
  6. }
  7. }
  8. // new.target is Rectangle
  9. var obj = new Rectangle(3, 4); // outputs true

This code shows that new.target is equivalent to Rectangle when new Rectangle(3, 4) is called. Class constructors can’t be called without new, so the new.target property is always defined inside of class constructors. But the value may not always be the same. Consider this code:

  1. class Rectangle {
  2. constructor(length, width) {
  3. console.log(new.target === Rectangle);
  4. this.length = length;
  5. this.width = width;
  6. }
  7. }
  8. class Square extends Rectangle {
  9. constructor(length) {
  10. super(length, length)
  11. }
  12. }
  13. // new.target is Square
  14. var obj = new Square(3); // outputs false

Square is calling the Rectangle constructor, so new.target is equal to Square when the Rectangle constructor is called. This is important because it gives each constructor the ability to alter its behavior based on how it’s being called. For instance, you can create an abstract base class (one that can’t be instantiated directly) by using new.target as follows:

  1. // abstract base class
  2. class Shape {
  3. constructor() {
  4. if (new.target === Shape) {
  5. throw new Error("This class cannot be instantiated directly.")
  6. }
  7. }
  8. }
  9. class Rectangle extends Shape {
  10. constructor(length, width) {
  11. super();
  12. this.length = length;
  13. this.width = width;
  14. }
  15. }
  16. var x = new Shape(); // throws error
  17. var y = new Rectangle(3, 4); // no error
  18. console.log(y instanceof Shape); // true

In this example, the Shape class constructor throws an error whenever new.target is Shape, meaning that new Shape() always throws an error. However, you can still use Shape as a base class, which is what Rectangle does. The super() call executes the Shape constructor and new.target is equal to Rectangle so the constructor continues without error.

I> Since classes can’t be called without new, the new.target property is never undefined inside of a class constructor.