ES6 class ≠ classical inheritance

Thai Pangsakulyanont
6 min readOct 20, 2015

--

I’ve read several articles by Eric Elliott on JavaScript, such as 10 Interview Questions Every JavaScript Developer Should Know and The Two Pillars of JavaScript.

He emphasizes that classical inheritance is dangerous and should not be used (which I agree), but somehow, the class syntax also gets the blame. In fact, it seems to me that the terms classical inheritance, class, and ES6 class were used interchangeably.

And that is totally wrong.

So I’ll say it again:

ES6 class ≠ classical inheritance

While I’m a big fan of functional programming and composition over inheritance, I’m also a fan of ES6 class syntax, because it has a nice syntax to describe ‘a class of similar-looking objects.’

Let’s start with an example from Common Misconceptions About Inheritance in JavaScript:

Let’s compare that to ES6 class version:

In the ES6 version, it is very clear that we are describing a class of similar-looking object that represents an Animal.

Both versions are essentially the same, but there are the differences.

  • The prototype of the Animal goes into `Animal.prototype`, which makes perfect sense — this is the prototype of an animal.
  • You instantiate it using `new Animal` instead of `Object.create(animal)` — this is the shorter and more idiomatic way.
  • Factories are implemented as static methods, which is also fine.

As you can see, the first example is just a way to simulate the second example, but without using the `class` keyword.

But these two examples both represent a class of similar objects, and they are both implemented via prototypical inheritance (remember that classes in ES6 are just syntactic sugar for a specific case of prototypical inheritance).

No wonder why ES6 classes are less flexible than using prototypical inheritance, because a class is but a specialization of prototypical inheritance.

Avoiding the Pitfall

You avoid the pitfall, not by avoiding the class syntax, but by avoiding this:

class Mouse extends Animal {

That’s where chaos begin. Avoid the temptation to create a subclass of anything unless you are absolutely sure that it would not make sense at all otherwise.

In that post, Eric also described a situation where he debugged a 6-level class hierarchy. This is an unfair comparison, as you can also end up in the same situation by using prototypical inheritance. Remember the Angular scope inheritance chain?

Again, the class syntax is not to blame, it’s the inheritance hierarchy and how so many people are taught that inheritance is a good way of working with objects. I have recently seen some people even claimed that inheritance is the real OO way, which makes me sad.

When to use classical inheritance

I think that it is fine to use classical inheritance, but only when it would make no sense at all to do anything else. I’ll elaborate on that with few counter-examples first.

I have a class of objects that support plugins. Should I inherit them from Tapable? No, not yet. You can just expose it as a property:

class SortableList {
constructor() {
this.plugins = new Tapable()

Note: You can also mix them in, if that’s your thing. For me, I prefer to keep the number of properties as small as possible.

Now, these lists must also emit events. Should this class extend EventEmitter? No, because it still makes sense to put the EventEmitter as a property:

class SortableList {
constructor() {
this.plugins = new Tapable()
this.events = new EventEmitter()

And now my SortableList possesses two functionalities at the same time!

You see, you can add as many capabilities as you want to this class, by virtue of composition. You don’t need to inherit anything.

And I believe this is what people mean when they say…

“Favor object composition over class inheritance.”

In most cases, it makes sense to use composition and you don’t need to inherit anything. This has nothing to do with whether you use `class` or not. Why?

ES6 class ≠ classical inheritance.

You can compose objects perfectly fine using an ES6 class.

Now, for another example, I’m writing a React application, and now I need to create a React component.

In this case, it doesn’t make sense to inherit from anything else, other than React.Component.

So what do you do?

class AppRoot extends React.Component {

What I’m creating is not just an object that has some rendering capability or whatever… No, no, no — it’s a React component, created just to be used with React.

Note: This doesn’t apply to pure function components introduced with React v0.14. In that case, you should always use pure functions when applicable.

Another note: React components are composable, so you should never inherit from a subclass of React.Component. Instead, just use composition.

Of course, you can create React classes without the class syntax, but if you do this only to avoid the ES6 class syntax, you’re deceiving yourself.

But if you’re using `React.createClass` so that you can benefit from autobinding and mixin thingy, as well as other goodies, well, go for it! (I’m not a fan of mixins though).

The new keyword

There are also complaints about the `new` keyword that kinda complicates things, but I look at it this way: the new keyword is part of the constructor’s signature.

When you do XMLHttpRequest, you know that when you call `open()`, you need to pass in the HTTP method first, the path second, and the asynchronous flag last. Why, because that’s the method’s signature. It’s exactly the same with constructor — you invoke it with new, because the signature says so.

Some say it’s verbose and unnecessary, but I think it makes your code more explicit.

I also do this with naming conventions, so it’s more unlikely to make mistakes.

For example, I prepend $ to every variable that refers to a jQuery-wrapped object. I append the kanji 川 at the end of every variable that points to a Bacon.js EventStream. And for constructors or anything that I should invoke with `new`, I named it with an uppercase.

ESLint has a new-cap rule which is very handy.

The dreaded `instanceof` operator

Well, just don’t use it.

Even in a language like Java, it’s considered a bad practice to do instanceof checks. It’s even scarier in JavaScript because instaceof lies.

Note that in the Bacon.js case in the the article linked above, given the design of Bacon.js that each Bacon EventStream depends on the assumption that there is only one ‘bacon machine,’ using instanceof checks in this case is actually a perfect way to enforce this.

Using `instanceof` is like testing for browser capabilities by parsing the `navigator.userAgent` string. It’s a long-known client-side wisdom that you should use feature detection instead.

So, it’s universally known that instanceof checks are somewhat bad. So, just don’t use it; use duck-typing and polymorphism. ES6 classes, on the other hand, is nice and cool.

How about functional programming?

Being “functional” has absolutely nothing to do with using or not using classes, because again, ES6 class is just an abstraction over a specific way we create functions and constructors.

The Promise is a prime example. It’s created with `new`, and encapsulates a result of the side-effect. It’s effectively the IO monad.

Functional programming and classes can coexist. Java 8 is making it much easier to pass methods to higher-order functions with method references, and so does ES7, with the function bind syntax proposal.

An instance method of a class is just a function, so they can be composed together just like any other function (although the method must have the correct `this` binding to make sense).

Conclusions

  • In JavaScript, classical inheritance and prototypical inheritance are not totally different things. Classical inheritance can be simulated with prototypical inheritance, and thus, is just a specialization of prototypical inheritance.
  • The ES6 class syntax is nothing more than a syntactic sugar that abstracts a very common pattern of describing similarly-looking objects in terms of class.
  • Favor object composition over class inheritance. In most cases where you might use inheritance, you could use composition instead. Do favor composition.
  • ES6 class ≠ classical inheritance.

ES6 class ≠ classical inheritance

--

--

Thai Pangsakulyanont

(@dtinth) A software engineer and frontend enthusiast. A Christian. A JavaScript musician. A Ruby and Vim lover.