Complex class polymorphism and lazy-loaded components I'm having an issue defining a base class and child classes, which handle initialization differently. I want it to work in ES6 but allow babel to transpile to ES5, and currently babel does not have a backwards compatible translation for new.target so I'm looking for something "close enough".

class Complex {
  constructor(real, imag, abs, arg) {
    if (new.target === Complex) {
      real = arguments.length &gt; 0 &amp;&amp; real !== undefined ? real : 0;
      imag = arguments.length &gt; 1 &amp;&amp; imag !== undefined ? imag : 0;
      abs  = arguments.length &gt; 2 &amp;&amp; abs  !== undefined ? abs  : Math.sqrt(real * real + imag * imag);
      arg  = arguments.length &gt; 3 &amp;&amp; arg  !== undefined ? arg  : Math.atan2(imag, real);

      this.real = real;
      this.imag = imag;
      this.abs  = abs &gt;= 0 ? abs : -abs;
      // limits arg between (-pi, pi] and also corrects phase for negative abs
      this.arg  = arg ? -((-arg + (abs &gt;= 0 ? Math.PI : 0)) % (Math.PI * 2) - Math.PI) : abs &lt; 0 ? Math.PI : 0;
    }
  }

  // ... all mathematical static and non-static methods will go here
}

Complex.Cartesian = class Cartesian extends Complex {
  constructor(real = 0, imag = 0, abs, arg) {
    super();

    this.real = real;
    this.imag = imag;

    if (arguments.length === 4) {
      Object.defineProperties(this, {
        abs: {
          configurable: true,
          enumerable: true,
          value: abs &gt;= 0 ? abs : -abs,
          writable: true,
        },
        arg: {
          configurable: true,
          enumerable: true,
          value: arg ? -((-arg + (abs &gt;= 0 ? Math.PI : 0)) % (Math.PI * 2) - Math.PI) : abs &lt; 0 ? Math.PI : 0,
          writable: true,
        },
      });
    }
  }

  get abs() {
    const value = Math.sqrt(this.real * this.real + this.imag * this.imag);

    Object.defineProperty(this, 'abs', {value, configurable: true, enumerable: true, writable: true});

    return value;
  }

  get arg() {
    const arg = Math.atan2(this.imag, this.real);
    const value = arg ? -((-arg + Math.PI) % (Math.PI * 2) - Math.PI) : 0;

    Object.defineProperty(this, 'arg', {value, configurable: true, enumerable: true, writable: true});

    return value;
  }
};

Complex.Polar = class Polar extends Complex {
  constructor(abs = 0, arg = 0, real, imag) {
    super();

    this.abs = abs &gt;= 0 ? abs : -abs;
    this.arg = arg ? -((-arg + (abs &gt;= 0 ? Math.PI : 0)) % (Math.PI * 2) - Math.PI) : abs &lt; 0 ? Math.PI : 0;

    if (arguments.length === 4) {
      Object.defineProperties(this, {
        real: {
          configurable: true,
          enumerable: true,
          value: real,
          writable: true,
        },
        imag: {
          configurable: true,
          enumerable: true,
          value: imag,
          writable: true,
        },
      });
    }
  }

  get real() {
    const value = this.abs * Math.cos(this.arg);

    Object.defineProperty(this, 'real', {value, configurable: true, enumerable: true, writable: true});

    return value;
  }

  get imag() {
    const value = this.abs * Math.sin(this.arg);

    Object.defineProperty(this, 'imag', {value, configurable: true, enumerable: true, writable: true});

    return value;
  }
};


The general idea is to have a base class Complex() that handles initialization of all complex components (real, imag, abs, arg) when invoked directly, but not when invoked by child classes Cartesian() or Polar(), which use lazy-loaded abs and arg, or real and imag respectively.

My idea is to use this instanceof Object &amp;&amp; this.constructor === Complex instead of new.target, but I'm not sure if that handles all edge cases exactly the same.

Any other advice not directly related to my specific issue is also welcome.

The code above is under MIT license, so if you plan on "borrowing" it for anything, please provide credit accordingly.
I ended up side-stepping the issue entirely by extending the Complex class with ES5 prototype chaining instead of ES6 extends:

class Complex {
  constructor(real = 0, imag = 0, abs = Math.sqrt(real * real + imag * imag), arg = Math.atan2(imag, real)) {
    this.real = real;
    this.imag = imag;
    this.abs  = abs &gt;= 0 ? abs : -abs;
    this.arg  = arg ? -((-arg + (abs &gt;= 0 ? Math.PI : 0)) % (Math.PI * 2) - Math.PI) : abs &lt; 0 ? Math.PI : 0;
  }

  // ...
};

const Cartesian = Complex.Cartesian = function Cartesian(real = 0, imag = 0) {
  this.real = real;
  this.imag = imag;
};

Cartesian.prototype = Object.create(Complex.prototype, {
  constructor: {
    configurable: true,
    enumerable: false,
    value: Cartesian,
    writable: true,
  },
  abs: {
    configurable: true,
    enumerable: true,
    get() {
      const value = Math.sqrt(this.real * this.real + this.imag * this.imag);

      Object.defineProperty(this, 'abs', {
        configurable: true,
        enumerable: true,
        value,
        writable: true
      });

      return value;
    },
    set(value) {
      Object.defineProperty(this, 'abs', {
        configurable: true,
        enumerable: true,
        value,
        writable: true,
      });

      return value;
    },
  },
  arg: {
    configurable: true,
    enumerable: true,
    get() {
      const arg = Math.atan2(this.imag, this.real);
      const value = arg ? -((-arg + Math.PI) % (Math.PI * 2) - Math.PI) : 0;

      Object.defineProperty(this, 'arg', {
        configurable: true,
        enumerable: true,
        value,
        writable: true,
      });

      return value;
    },
    set(value) {
      Object.defineProperty(this, 'arg', {
        configurable: true,
        enumerable: true,
        value,
        writable: true,
      });

      return value;
    },
  },
});

const Polar = Complex.Polar = function Polar(abs = 0, arg = 0) {
  this.abs = abs &gt;= 0 ? abs : -abs;
  this.arg = arg ? -((-arg + (abs &gt;= 0 ? Math.PI : 0)) % (Math.PI * 2) - Math.PI) : abs &lt; 0 ? Math.PI : 0;
};

Polar.prototype = Object.create(Complex.prototype, {
  constructor: {
    configurable: true,
    enumerable: false,
    value: Polar,
    writable: true,
  },
  real: {
    configurable: true,
    enumerable: true,
    get() {
      const value = this.abs * Math.cos(this.arg);

      Object.defineProperty(this, 'real', {
        configurable: true,
        enumerable: true,
        value,
        writable: true,
      });

      return value;
    },
    set(value) {
      Object.defineProperty(this, 'real', {
        configurable: true,
        enumerable: true,
        value,
        writable: true,
      });

      return value;
    },
  },
  imag: {
    configurable: true,
    enumerable: true,
    get() {
      const value = this.abs * Math.sin(this.arg);

      Object.defineProperty(this, 'imag', {
        configurable: true,
        enumerable: true,
        value,
        writable: true,
      });

      return value;
    },
    set(value) {
      Object.defineProperty(this, 'imag', {
        configurable: true,
        enumerable: true,
        value,
        writable: true,
      });

      return value;
    },
  },
});


This allowed me to write the constructors without requiring the call to super(), which was what created the issue in the first place.