In an effort not to pollute or extend the prototype Number
object, I am attempting to create a class that extends Number
. However, when I do this and then try to add anything to it or even add it to itself, it is converted back to a number.
I understand this is occurring because it is taking the numeric valueOf
property value and returning that value added to another number, but I would like for the value to always retain its subclass without having to create additional methods like:
.add(x)
.sub(x)
.mul(x)
.div(x)
Is there a way to achieve this, similar to how both the Number
and BigInt
types allow for these types of computations? I could—but would prefer not to—extend the built-in Number object to support my custom methods, but anytime I try working with them now, they lose all their methods.
For example:
let num = 5;
console.log(num.constructor); // Number
num += 2;
console.log(num); // 7
console.log(num.constructor); // still Number
However, working with my extended class:
class MyNum extends Number {
constructor (num = 0) {
super(num);
}
get isEven() { return this.isInt && this % 2 === 0 }
get isOdd() { return this.isInt && this % 2 !== 0 }
}
let num = new MyNum(5);
console.log(num.constructor); // MyNum
num += 2;
console.log(num); // 7
console.log(num.constructor); // back to Number
Furthermore, if the only solution here is to add methods like .add(x)
, would I need to recreate a new class instance whenever a computation like .add(x)
is done, or is there another more native way to add to the existing object's value.
If the below is the only workaround, I may stick to extending the built-in Number
object:
class MyNum extends Number {
constructor (num = 0) {
super(num);
}
add(x) { return new MyNum(this.valueOf() + x) }
sub(x) { return new MyNum(this.valueOf() - x) }
mul(x) { return new MyNum(this.valueOf() * x) }
div(x) { return new MyNum(this.valueOf() / x) }
mod(x) { return new MyNum(this.valueOf() % x) }
get isEven() { return this.isInt && this % 2 === 0 }
get isOdd() { return this.isInt && this % 2 !== 0 }
}
If you want
num += 2;
to result in num
being a MyNum
before, and a MyNum
after, I'm afraid it isn't possible. As the specification says, all values will be converted to primitives (valueOf
, toString
), after which the primitives are either concatenated together or added together - and the concatenation or addition of primitives will result in plain primitives as the result, either a string, number, or BigInt.
Adding new explicit methods like .add
is the only real option.
The value of a number object is in an internal slot which isn't changeable (not that you'd want to anyway, since mutation could be confusing). I don't think there's a better approach than add(x) { return new MyNum(this.valueOf() + x) }
etc. But you can slightly clean up your methods by removing the valueOf
calls, there's no need for them.
add(x) { return new MyNum(this + x) }
sub(x) { return new MyNum(this - x) }