IME JS classes are less about inheritance (which remains compositional anyway, though classes do make these easier what with not having to maintain the prototype chain or hand-roll super calls) and more about the various syntactical convenience of class declarations, namely that you don't have to deal with the Function / Function.prototype crap, the hand-rolling of `new` mandates (a class ctor is always called through new), the requirement of explicit strict mode (class declarations are always strict), method shorthands (they do exist on object literals, they don't exist when adding to a prototype property by property), …
const createCounter = ({ startAt = 0, incremementBy = 1 }) => {
let count = startAt
return {
currentCount: () => count,
step: () => {
count += incremementBy
}
}
}
const counter = createCounter({ startAt: 5 })
counter.step()
versus class Counter {
constructor({ startAt = 0, incrementBy = 1 }) {
this.startAt = startAt
this.incrementBy = incrementBy
this.count = 0
}
step() {
this.count += this.incrementBy
}
}
const counter = new Counter({ startAt: 5 })Methods with arrow syntax offer implicit function binding and TypeScript adds abstract classes as well as abstract, private, protected and readonly fields. New language features aren’t supposed to replace simple logic that has always worked. They are there to provide new options for programming patterns.
I think the last time I personally abused inheritance was when using Backbone.js more than 5 years ago, and that experience was what finally tipped me over the edge to start learning more about functional programming.
You can go much farther and discover that OO state mutations across multiple methods can be replaced with higher order functions that let you keep state mutations local, within context of each function. Suddenly OO becomes this weird very unproductive and error prone way of writing and reading code. An entire OO concept is just bad, always was, nothing can fix it.
At least with Javascript higher order programming was always possible.
IMHO inheritance and especially deep inheritance hierarchies are almost always the wrong thing to do. I know of few exceptions and have ended up regretting breaking that rule on the few occasions I allowed that to happen. These days I just don't use it at all on principle. I'm very weary of using frameworks that depend on or over use inheritance. E.g. much of Spring looks problematic and convoluted to me. I use it but I tend to avoid the stuff that is too hard to understand. Five or more layers of classes is just insanely misguided IMHO.
Most of those layers are just working around the previous (wrong) decision to use inheritance. It's like the broken windows theory. Inheritance leads to more inheritance. It causes all sorts of problems down the line and you can't change it once you have users extending your classes.
I love that modern languages disable this. E.g. Kotlin has closed classes by default (the opposite of Java) and does not allow inheritance between data classes at all. This is great and communicates clearly that you should not be littering your code base with 'open'. This encourages good design that does not depend on scattering functionality all over the inheritance hierarchy or the endless levels of indirection. They even provide a compiler plugin to deal with spring that re-opens classes at compile team so that Spring and other legacy Java framworks can do it's dirty business of reflectively extending classes without requiring the code to be compromised.
I find it ironic that e.g. the Javascript community has worked hard to get classes and inheritance and are essentially repeating all the same mistakes people were making with Java 20 years ago by overusing inheritance. Most of 'modern' javascript to me looks like a blast from the past.