The way of the prototype
I almost started this post with a cheesy phrase such as "Could you imagine a world without classes?"
then, I realized it wouldn’t be so hard to do so since a great part of the programming community is focused on writing a lot of JavaScript. I know sometimes it’s regarded as an obscure language that resembles more a hack than a well thought programming language (I'm looking at you Weak dynamic typing!), but an interesting feature often overlooked is that it uses a prototype-based model rather than a class-based one. But then again, what does that offer us?
A philosophical detour
If we think of software as the craft of gaining knowledge (that is, my software encodes in a formal language all the things I learnt about the domain problem and how to solve it) we can gain some insights from the philosophers that studied that field in their careers. Actually, the two models we're comparing are based on two different philosophical schools.
For the class based inheritance we have Plato and Aristotle who claimed that every object we can feel in the material world (in coding terminology: instances) is an image or a copy of an ideal object (a class, for us). Aristotle did his part categorizing a lot of these ideal objects in a thorough taxonomy.
On the other hand we have Wittgenstein, who said "Hey, but that model has some exceptional objects that cannot fit in just one category! I think the human gains knowledge by holding a prototypical concept and then comparing new ones against it". In other words, if I have never seen a plane, the first time I find one I will hold that knowledge as my prototypical concept (for me, the concept of plane is no different of that plane in particular). If in the future I spot a helicopter I could say "It is just like a that thing that I saw before (in the sense that it flies) but with a different propulsion mechanism".
Back to business
Even when ES6 offers us all those class
and extends
keywords, there is no such thing as classes in JavaScript. Every object has a prototype that will answer for it in case it doesn't understand the message it received. So we will define objects somewhere among the lines of the definition of the plane above: it is like this other object (the prototype) but it differs from it in these particular things. Enough mumbling... to the code!
const john = {
knowsHowToMakeACake() {
return this.knowsHowToReadARecipe() && this.knowsHowToBake();
},
knowsHowToReadARecipe() {
return false;
},
knowsHowToBake() {
return false;
}
};
const paulTheBaker = Object.create(john);
paulTheBaker.knowsHowToReadARecipe = function () {
return true;
}
paulTheBaker.knowsHowToBake = function () {
return true;
}
const me = Object.create(john);
me.knowsHowToReadARecipe = function () {
return true;
}
Interesting bits:
- I only stated in which ways an object is different to it's prototype, so in the case of me I'm saying that I'm just like the prototypical human[1] (john) but I do know how to read a recipe.
- Notice how the
this
keyword in the knowsHowToMakeACake method refers always to the object that received the message, so
john.knowsHowToMakeACake(); // -> false
paulTheBaker.knowsHowToMakeACake(); // -> true
me.knowsHowToMakeACake(); // -> false
That's great and all but... what did I gain? This example might lead you to think that there is not much improvement, and you would be right. I'm not stating that one model is better than the other, it’s just a different approach.
Pros of prototypical sharing
- Less (defined) objects: we have to define less objects in order to have a working example (I mean, we don't have to create the class object for every new object we need, like in the example above I created ‘human-like’ objects without creating a ‘Human’ class)
- Well suited for experimentation and discovery phases: If we're learning about the domain we're not forced to categorize objects right away. We wouldn’t know what hierarchy of classes those objects should belong to yet!
- No one-instance-classes: This is a somehow contrived case, but sometimes in the classes world we have a class that will end up being instantiated just once (and I'm not talking about the Singleton pattern, but it could also help with that case). In the prototype world... well, it's just another object!
- It favors composition over inheritance: When an object doesn’t understand a message it will be delegated to it’s prototype to answer for it (but any reference to
this
in the prototype’s code will refer to the object that received the message). - It plays well with methodologies like TDD: Coding the minimal solution that works in a class environment will sometimes force you to define classes for the snippet to compile, even if you don’t know what they should represent at the moment.
Cons
- It's not that clear what organization arises from the model: Most of the times, the categorization of cases is useful for us to abstract away some knowledge and, sometimes, in the prototype approach it isn't that obvious (or clear for other programmers) what are all the core concepts of a bunch of objects that share some common behaviour. In the class-based example, all of them implement a particular interface or extend from the same base class.
- Tooling for some languages[2] isn't great (yet): Tooling is heavily based on classes’ static analysis, so IDEs have a bit of trouble finding all the objects that have the same prototype and doing some refactors to them (I know the dynamic nature of JavaScript doesn't help in that case either).
Wrapping up
Prototype-based programming is an alternative to class-based inheritance and it’s the one used in Javascript (and Typescript as well, though they’re trying very hard to hide it). It doesn’t force you to come up with abstractions at the very beginning, so it fits naturally with an incremental approach of development and you can achieve everything that you could do with classes.
If you want to read more about it, here is a non-exhaustive and very interesting list of resources to look at:
- The Self programming language was the first to implement these ideas in a formal language
- Kyle Simpson coined the term “Objects linked to other objects” in order to talk about prototypes in this chapter of his book. You should definitely check it out, there are some good diagrams explaining the relations that take place in a class-based inheritance plus he compares them with the ones that appear in the prototypical model.
- There is a framework that implements prototypes in Smalltalk called Denotative object made by uncle Wilki and Máximo Prieto.
There are several ways to define an object’s prototype such as Object.create, Object.setPrototypeOf or defining _proto_ right into an object literal. ↩︎
Languages like Self have excellent tools for working with prototypes, and several projects were made to bring good development environments to Smalltalk. ↩︎