The issue in your code stems from inheriting a base class with a subclass that does not have the same constructor argument list as the base class. Therefore, calling `new this.constructor(...args)` is risky for any arguments passed. For instance:
class StringPoint extends Point2D {
constructor(x: string, y: string) {
super(x.trim().length, y.trim().length);
}
}
const s = new StringPoint("abc", "defgh ");
console.log(s.x) // 3
console.log(s.y) // 5
In the example above, the `StringPoint` class extends `Point2D`, but its constructor takes two strings instead of numbers. This code works fine until you invoke a method from the base class that calls `new this.constructor`, resulting in a runtime error:
console.log(s.multiply(2)) // 💥 ERROR! x.trim is not a function
This error occurs because `s.multiply(2)` ends up executing `new this.constructor(6, 10)` where `this` refers to an instance of `StringPoint`, causing `this.constructor` to be `StringPoint`. As a result, `new StringPoint(6, 10)` is called, passing numbers when strings are expected, leading to a `trim()` method error.
To rectify this issue, it is advisable to use the `Point2D` constructor directly instead of relying on the instance's constructor:
public add(that: Point2D) {
return new Point2D(this.x + that.x, this.y + that.y); // okay
}
public subtract(that: Point2D) {
return new Point2D(this.x - that.x, this.y - that.y); // okay
}
public multiply(scalar: number) {
return new Point2D(this.x * scalar, this.y * scalar); // okay
}
This approach works since `Point2D` specifically expects two numbers as arguments. The previous `StringPoint` example now functions correctly without issues because the base class methods always create instances of the base class and do not attempt to construct incompatible subclasses:
const s = new StringPoint("abc", "defgh ");
console.log(s.x) // 3
console.log(s.y) // 5
console.log(s.multiply(2)) // okay, {x: 6, y: 10}
Here, `s.multiply(2)` returns a `Point2D` object rather than a `StringPoint` object.
It's essential to note that TypeScript's typings for the `constructor` property can sometimes be misleading; `this.constructor` is considered to be of type `Function` or an "untyped call." This situation is both too strict and too loose, potentially leading to errors at runtime.
When handling the `constructor` property in TypeScript, caution should be exercised to avoid unexpected behavior and errors.
Link to Playground