Modern Javascript is a lot different to Javascript of 2010. We have considerably more methods and means of doing things that we did not have previously. Some of those include classes, generators, arrow functions and a few other high-profile additions.
One aspect of Javascript that not even ES2015 nor ES2016 covers is the concept of abstract classes. The ability to specify a class that defines how child classes should look, more specifically ensuring they specify certain methods.
Recently whilst working on an Aurelia project, I was building a widget based system where a base widget class defines a few core values and methods, then the child class which is a certain type of widget extends and defines the contents of the widget.
One such requirement was the base widget class cannot be instantiated. Another requirement is all child widgets must implement a schema method which returns the schema for a particular widget.
Preventing base class from being instantiated
Due to how classes are implemented in ES6, we can check the constructor knowing that any class that extends the Widget class is going to have a different constructor because it’s a new class and therefore a new instance. We just check in the base widget class if the constructor is “Widget” or not. If it is, we can assume someone is trying to instantiate the widget class directly via: let myInstance = new Widget()
which our code will stop.
class Widget { constructor() { if (this.constructor === Widget) { throw new TypeError('Abstract class "Widget" cannot be instantiated directly.'); } } }
Enforcing implented methods
To require an inherited child class implements our required method, we can check for its existence via this
which allows us to see if the required method has been defined. We will take the above example and add in another check for our child class method.
class Widget { constructor() { if (this.constructor === Widget) { throw new TypeError('Abstract class "Widget" cannot be instantiated directly.'); } if (this.schema === undefined) { throw new TypeError('Classes extending the widget abstract class'); } } } class PieChart extends Widget { constructor() { super(); } get schema() { return { "name": "PIE_CHART", "data": [] } } }
Conclusion
We might eventually get a native solution, but for the moment we can easily implement our own fake abstract class implementation in Javascript using classes and it will work for most purposes. I haven’t encountered any issues with the above solutions.
I think as the JavaScript community starts to embrace classes and related concepts – we will start to see more parity between ES6 classes and classes from languages like C#, Java etc.
Right now I am just happy to see classes as a concept make it into ES6, but I would definitely like to see more advanced structures like abstract, scope modifiers, overriding etc make it in as well. Not sure the UI community will push for it, but server side JS will likely miss it at some point.
Nice article. Correct me if I’m wrong but the constructor check is legal ES5 right?
this.constructor === Widget
May be using new.target.name over this.constructor.
A nicely crafted write up. Thanks !
you may want to raise an error only on method call
class AbstractClass {
subclassResponsibility() {
throw new Error(‘subclass responsibility’)
}
}
class Widget extends AbstractClass {
aMethodToOverride() {
this.subclassResponsibility()
}
}