Qwertiy
Qwertiy

Reputation: 21400

Extend object wrappers for modern primitives

Starting from ES6 classes can extend special objects like functions, arrays and primitive's wrappers. It's simple: just write a class that extends corresponding type and use it:

class MyNumber extends Number {
  constructor() {
    super(42)
  }
  
  square() {
    return this ** 2
  }
}

var x = new MyNumber()

console.log(typeof x, x + "", x.square() + "")

But EcmaScript also has some new types like Symbol and BigInt. They still have a non-primitive wrapper types, but you can't use them as constructor and need to explicitly wrap primitive into an object:

var x = BigInt("42")
var y = Object(x)

console.log(typeof x, x + "", x + 1n + "")
console.log(typeof y, y + "", y + 1n + "")

try {
  var z = new BigInt("42")
} catch (e) {
  console.log(e.message)
}

What if I want to extend such wrapper? Defining a class works fine, but if I try to create an object, it throws on super call:

class MyBigInt1 extends BigInt {
  constructor() {
    super("42")
  }
}

try {
  var x = new MyBigInt1()
} catch (e) {
  console.log(e.message)
}

class MyBigInt2 extends BigInt {
  constructor() {
  }
}

try {
  var x = new MyBigInt2()
} catch (e) {
  console.log(e.message)
}

Upvotes: 1

Views: 205

Answers (2)

Bergi
Bergi

Reputation: 664559

From the spec:

The Symbol constructor […] is not intended to be subclassed.

And similarly from the BigInt proposal:

The BigInt constructor is not intended to be used with the new operator or to be subclassed.

Instantiating primitive wrappers is already bad enough, don't go extending them.

A hack would be to not call super() (which you cannot prevent from throwing), but create the object yourself (without new) and then set its prototype to your custom one. Just like you already did it in your answer :-)

Upvotes: 1

Qwertiy
Qwertiy

Reputation: 21400

I've found a possible solution, but still searching for a better way:

class MyBigInt extends BigInt {
  constructor() {
    var res = Object(BigInt("42"))
    Object.setPrototypeOf(res, new.target.prototype)
    return res
  }
  
  square() {
    return this ** 2n
  }
}

var x = new MyBigInt()

console.log(typeof x, x + "", x.square() + "")

And same approach with Symbol

class MySymbol extends Symbol {
  constructor(description) {
    var res = Object(Symbol(description))
    Object.setPrototypeOf(res, new.target.prototype)
    return res
  }
  
  smth() {
    return `(${this.description})`
  }
}

var x = new MySymbol("qqq")

console.log(typeof x, x.description, x.smth())

var y = { [x]: 42 }

console.log(y[x], y[x.toString()])

Upvotes: 0

Related Questions