ES6 Class is a Lie: Understanding Prototype Delegation
I’ll lay my cards out on the table: personally, I think classes are overrated, and for reasons I admit may be partially aesthetic, I just don’t like them. I’m not the only one who thinks so. I very much prefer a more functional approach, and even when I want or need to mix data and behavior, I prefer other alternatives made possible by the flexibility of JavaScript, like Crockford objects, factory functions, or just object literals, or the revealing module pattern, which you may be using without even realizing it, simply by exporting functions from a file and importing them in another.

But I promise, this is an educational article, not a click-bait flame war piece.
There are two main related but distinct arguments for avoiding the “class” keyword, as well as keywords like “this” and “new”, in JS:
(1) It’s a lie. Why would you want to write code that is full of lies? Even if you educate yourself well enough to avoid the misunderstandings and potential mistakes, shouldn’t we strive to write code that clearly communicates our intentions?
(2) There are, as mentioned, other ways of accomplishing what you’re trying to accomplish, using more idiomatic JS that takes advantage of the way JS works rather than trying to paper over and hide it. These alternatives may have advantages, at least in some situations. Even if they don’t, using them may help you to understand JS at a deeper level, and to broaden your horizons by seeing new ways of solving problems, instead of helping you continue to get away with not doing so.
But whether you agree with these arguments or not… Whether you write classes in JS all the time and love it, or aim for a pure FP style, and only use the “new” keyword begrudgingly when you are forced to by a third party library, or anything in between, it behooves you to understand how “classes” actually work in JS, and why it is completely different from classes in other OOP languages.
To use tools most effectively, we need to understand how they work, regardless of how we think they should work or how we would like them to work. To be proficient at JS, therefore, it is vital to understand prototypal inheritance.
Which brings us to the first reason you might want to avoid using ES6 “classes”: They’re fake. In 2021, JS does not have classes, at least not REAL classes. What it has is syntax sugar for the exact same prototype inheritance JS has always had.
// "Class" syntax
class Dog {
voice = "Woof!";
speak() {
console.log(this.voice);
}
}// Constructor function
function Perro() {
this.voz = "Guau!";
}Perro.prototype.hablar = function () {
console.log(this.voz);
};// Usage
const fido = new Dog();
fido.speak();const firulais = new Perro();
firulais.hablar();
What’s the difference (aside from the second one being in Spanish)? None. The former is merely syntax sugar for the later.
But doesn’t the first one look prettier? Well, I guess, maybe. Seems pretty subjective. Easier to use? Really? I don’t actually see how, if you spend, like, 5 minutes learning how to use the second form. (Especially with all the changes JS class syntax has gone through over the years.) I suspect the primary motivation is that the first one looks more comfortingly familiar to Java and C# etc. developers.
Which is the problem. Because it’s a lie. Regardless of what you think about Java/C#, JavaScript, despite the name, is nothing like them and never can be. Therefore the syntax gives you the impression that it’s doing something it’s not, apparently by design. It’s “syntactic obscurantism”.
(This is actually the biggest problem with JS, in my opinion… It’s a secretly sort of brilliant language, admittedly with plenty of flaws… But the biggest flaw is that it bends over backwards to disguise itself as a mediocre knock-off of other languages. When in reality it is something that is arguably better…and of course arguably worse…but undeniably completely different.)
So what do I mean it’s not a “real” class? Well, if you know your OOP 101, you know that a class is a “type”. It’s like a blueprint. You can’t drive a blueprint of a car; you can only use the blueprint to build an actual car, which you then can drive. Likewise, to use a class (unless it’s a static class) you first have to instantiate that class, thus creating an actual object, AKA an instance.
So how is fake class-like behavior achieved in JS? With prototypal inheritance. So how does prototypal inheritance work? Via the prototype chain.
And this is the key point: classical inheritance and prototypal inheritance are fundamentally different inheritance models. In short, more or less, classical inheritance involves creating copies based on a template; prototypal inheritance, on the other hand, allows for code sharing by linking objects.
Hence JS’s style of OOP can be described with the acronym OLOO — objects linked to other objects. You could argue, as Kyle Simpson and others do, that it’s therefore more “object-oriented” than “classical” OOP languages (i.e. Java and MS Java, I mean C#), which could be more accurately described as Class-Oriented Programming (COP?). But there’s no prize for being the most object-oriented, nor should there be. Love it or hate, it’s simply a different paradigm.
Every object contains a reference to the object that is that object’s prototype. When we call fido.speak(), the JS engine checks the “fido” object for a property called “speak”. It does not find one, so then it gets the object’s prototype, and looks for a property called “speak” on that object. And it finds one, so that’s the one it uses.
What if the prototype didn’t have “speak”? It would get the prototype’s prototype and try to find speak there. And it would not find one there. So it would check the prototype’s prototype’s prototype… Oops, the prototype’s prototype doesn’t have a prototype, so the value of speak is undefined. (Note that in JS, confusingly, undefined is a value.) What happens when you try to call undefined? Well, undefined is not callable, so you get a TypeError.
fido.bark(); // Uncaught TypeError: fido.bark is not a function
console.log(fido); // Dog {voice: 'Woof!'}
console.log(fido.__proto__); // {constructor: ƒ, speak: ƒ}
console.log(fido.__proto__.__proto__); // {constructor: ƒ,
// __defineGetter__: ƒ, __defineSetter__: ƒ,
// hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
console.log(fido.__proto__.__proto__.__proto__); // null
What is this .__proto__
(if you need to say it out loud, you can call it “dunder (short for ‘double underscore’) proto”). property? How is it different from the .prototype
(“dot prototype”) property we attach functions to in order to create “class methods” with pre-ES6 syntax? And what do either of them have to do with the [[Prototype]]
thing you might see in dev tools? Good questions! To be honest, I’m still figuring it out myself. I’ll get back to you.
The important point for now is that we can see that each object has a reference to the other, perfectly normal non-special object that is its prototype. Just an ordinary object reference contained in a more-or-less ordinary (but non-enumerable, ie Object.keys and for-in loops etc. won’t show it) property.
fido
is the Dog “instance” we created with new
. We see that fido’s only own property is voice
, but fido’s prototype has a speak
method, which we can call to log out fido’s voice. And by “method”, I mean: a function to which a reference is stored in a property of an object. Remember, in JS, functions are first class, whereas classes are not even economy class: they don’t really exist.
So what’s the prototype of the prototype? Well, we can see that it has, among other things, a method called hasOwnProperty
… Hmm… Sound familiar? It’s Object. And we see that the prototype of Object, (i.e., fido’s prototype’s prototype’s prototype) is…null. We’ve reached the end of the prototype chain (or the “top” of the “inheritance hierarchy”).
If you follow the link, you’ll see that it says, “Nearly all objects in JavaScript are instances of Object”. I would say that that terminology is misleading.
“Instances” imply abstract “types” of which something can be an instance, as in: “You do things that annoy me” is a general concept; “You left dirty clothes on the floor yesterday” is an instance of that. Your pet Fido is instance of the Platonic ideal Dog.
JS has no notion of “types” or “blueprints” in that sense. There are only objects (instances). Hopefully I’ve made it clear it by now: “object” and “instance” are synonyms. That is true in general, but doubly so in JS, because there is nothing to contrast them with; nothing for them to be instances of. So from now on, I’ll just say object.
Another quibble with the phrasing: The “nearly all” part is also basically true, but… You can easily create an object that does not inherit from Object if you want to, and you can change the prototype of any object at any time. Because, as we’ve established, an object’s prototype is simply another object, linked to by a reference contained in a property on the “child” object.
Why does that matter? Well, here’s one implication:
Object.prototype.sit = () => console.log("Good boy.");
fido.sit(); // "Good boy."Dog.prototype.sit = () => console.log("Woof woof, I'm already sitting.");
fido.sit(); // "Woof woof, I'm already sitting."
If you have internalized a classical inheritance model, and are thinking in terms of types vs concrete instances, this might be surprising, maybe even disturbing. If you understand prototypal inheritance, it should be completely obvious and expected.
It enables you to do things like this, although it’s widely considered to be a bad practice:
Array.prototype.double = function () {
return this.map((x) => x * 2);
};
console.log([1, 2, 3].double()); // [2, 4, 6]
For all the talk about how classical OOP supposedly models the real world… (Which I think is (A) not true, (B) not useful, even if it were true, since our purpose is to create programs that produce desired behavior, not necessarily to model anything…)
Prototypal inheritance seems to have a bit more in common with literal biological inheritance than classical inheritance. In reality, species don’t actually exist except as a…sort of statistical abstraction…more or less… Only individual organisms actually, concretely exist. They do not come into being when the Platonic essence of the species is “instantiated”… They are created when an individual organism procreates…i.e., it creates a new, individual organism.
And when a baby mammal doesn’t know how to catch food (does not have a catchFood
method as an own property), it “delegates” the task to its parent, who hopefully does.
Again, I do not think the purpose of programming languages is to “model reality”, so this is not an argument that prototypal inheritance is necessarily better. It’s just a metaphor to help understand how it works.
And the metaphor breaks down, because procreation implies a sort of copying with modification. True class-based inheritance involves copying, but when you create a new object from a prototype, it is not a copy. Shared behavior is attained by linking via a property that contains a reference.
If I’m being repetitive, it’s because it can’t be stressed enough: in JS, there are only individual objects. Prototypal inheritance is a mechanism for code reuse that works by linking objects in a prototype chain, ie OLOO — objects linked to other objects.
So that’s the basic concept. I’ve discovered I still have quite a bit yet to learn about the details of how it works. Once I figure it out, maybe I’ll explain in a new post.
Edit: I changed the title because I finished Kyle Simpson’s Advanced JavaScript course on Pluralsight, in which he said he doesn’t like the phrase “prototypal inheritance”, for the exact reason I just pointed out a few paragraphs ago: the concept of “inheritance” necessarily implies some sort of copying. The important thing to understand is that what we are actually doing is delegating: using methods on one object instance that are actually stored on another object instance, by linking object instances by reference via a special property (prototype).