javascriptecmascript-6ecmascript-next

How to make ES6 class final (non-subclassible)


Assume we have:

class FinalClass {
  ...
}

How to modify it to make

class WrongClass extends FinalClass {
  ...
}

or

new WrongClass(...)

to generate an exception? Perhaps the most obvious solution is to do the following in the FinalClass's constructor:

if (this.constructor !== FinalClass) {
    throw new Error('Subclassing is not allowed');
}

Does anyone have a more cleaner solution instead of repeating these lines in each class that supposed to be final (probably with a decorator)?


Solution

  • Inspect this.constructor in the constructor of FinalClass and throw if it is not itself. (Borrowing inspection of the this.constructor instead of this.constructor.name from @Patrick Roberts.)

    class FinalClass {
      constructor () {
        if (this.constructor !== FinalClass) {
          throw new Error('Subclassing is not allowed')
        }
        console.log('Hooray!')
      }
    }
    
    class WrongClass extends FinalClass {}
    
    new FinalClass() //=> Hooray!
    
    new WrongClass() //=> Uncaught Error: Subclassing is not allowed

    Alternatively, with support, use new.target. Thanks @loganfsmyth.

    class FinalClass {
      constructor () {
        if (new.target !== FinalClass) {
          throw new Error('Subclassing is not allowed')
        }
        console.log('Hooray!')
      }
    }
    
    class WrongClass extends FinalClass {}
    
    new FinalClass() //=> Hooray!
    
    new WrongClass() //=> Uncaught Error: Subclassing is not allowed

    ______

    As you say, you could also achieve this behaviour with a decorator.

    function final () {
      return (target) => class {
        constructor () {
          if (this.constructor !== target) {
            throw new Error('Subclassing is not allowed')
          }
        }
      }
    }
    
    const Final = final(class A {})()
    
    class B extends Final {}
    
    new B() //=> Uncaught Error: Subclassing is not allowed

    As Patrick Roberts shared in the comments the decorator syntax @final is still in proposal. It is available with Babel and babel-plugin-transform-decorators-legacy.