To reiterate, ES6 classes are primarily syntactic sugar casting prototypal delegation (which is lesser known) in the guise of class inheritance (which is widely known). OK, but what’s the harm in offering this shortcut to those who feel more comfortable using it? Well, it depends. Possibly fairly little – but just as likely, quite a bit.
For one thing, since ES6 classes aren’t actually classes under the hood, developers who assume that they are may be in for some nasty surprises, especially when coming from a statically typed language. The “unbound
this” problem outlined in part 2 is one thing, the fundamental misconception that you’re dealing with a type is another. This is precisely the sort of thing that is likely to lurk in the depths only to surface and bite your leg off when you least expect it.
class syntax. In other words, you may be limiting your view to a single paradigm which isn’t even an accurate representation of the underlying mechanisms, thus also limiting your ability to take better advantage of the platform where you’re working. Most especially, the risk for (over)reliance on direct inheritance is high in cases where a compositional approach may be more suitable, even from people who swear by the GoF in all other respects.
In the wild
But are those risks really something we need to take seriously? Can’t we trust developers to recognize them and mitigate them? Here I would answer “yes, absolutely” and “no, not necessarily”, respectively. Let’s illustrate.
Or even this:
We have also seen people fall into the methods-as-event-handlers trap, where the value of
this is unexpectedly (to them) overwritten. Since React was previously all (or nearly all) about classes, there is also no shortage of questions on the web about how and why component methods don’t work as expected when bound to JSX events, because the class definition simply doesn’t work like most people expect it to, and there are an almost equal number of articles about the pros and cons of the available mitigations. And this is despite the fact that the React documentation explicitly calls out this particular vulnerability – a vulnerability which only exists as a direct consequence of the ES6 class syntax for components, as it is not present when using
createReactClass() or hooks (see below), or in plain functional components.
On the one hand, much of the TC39 work being done nowadays, such as decorators and the previously touched-upon instance fields, focuses on class semantics over prototypes (or any other language construct, for that matter). Also, other web APIs such as Web Components are based on classes, with strongly coupled inheritance touted as the preferred extension mechanism. In other words, it seems as though the powers that be are determined to stay on the class track – some of them are even on record stating that requiring class syntax was the only way to get full buy-in on the Web Components spec, which from a technical standpoint is an artificially imposed limitation (check out hybrids if you’re as irked by it as many people in that issue thread).
new for many years, and a little search engine exercise will reveal that they are far (really far) from alone.
On the third hand (just go with it), several large frameworks have actually moved away from classes instead of toward them. As mentioned, React was previously very class-centric, with most component definitions extending the
React.Component class and a strong prevalence of class syntax in the official documentation, but since version 16.8 a few years back the recommended way forward is to use hooks instead, which are not supported in class components at all. In fact, the hook docs now go as far as saying that “classes confuse both people and machines”.
Versions 1 and 2 of Vue used object literals in component definitions, but in the early stages of the development of version 3 the plan was to adopt classes instead, building on the existing class component plugin. However, the team ran into numerous problems with this approach and in the end decided to scrap it entirely in favor of functional composition, which is what ended up as the preferred alternative to object literals in the released product last autumn.
class is indeed harmful.
getBigValue() example from part 2 would work exactly the same, for instance. And, if you’re coming from PHP you will probably recognize traits as a version of the mixin pattern (or vice versa). And so on.
What to do
If that sounds harsh, consider the upside: the better you know your tools, the wider the range of problems you can solve with them, and the smaller the collection of traps you’re likely to walk into. The point of this article is not to forbid the use of
Furthermore, due to the highly disparate nature of the JS ecosystem, you are pretty much guaranteed to run into prototypes and/or other non-class techniques in some project or other sooner or later, regardless of what you normally work on – and then you’d better know what to do with them. ES6 classes have been in the language since 2015, prototypes and closures since 1995. Better catch up!
Mimicking classical inheritance is just one way in which the prototype system can be used, and a subpar one at that – it is not the only way. In fact, you don’t have to use it at all, and instead opt for end-to-end object composition and/or pure functional programming. Don't treat everything as nails just because you like your hammer – have a look around in the toolbox first.