Not too long ago, I was also of the opinion that prototypal inheritance is better than classical inheritance. After all, prototypal can emulate classical, plus more. But lately I’ve started to believe that the inheritance mechanism could be – and perhaps should be – a low-level implementation detail that we needn’t know or care about. And the ES6 class syntax was, in part, responsible for my change in thinking.
class Animal {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}
class Dog extends Animal {
bark() {
console.log("Woof!");
}
}
var spot = new Dog('Spot');
If the language used classical inheritance, then the result of the above would be just one object – the instance object – containing the accumulated properties and functions.
spot:
---------------------------
| name | Spot |
| constructor | *function |
| sayName | *function |
| bark | *function |
---------------------------
And if the language used prototypal inheritance (as JavaScript does), then the result would be a series of linked objects.
Animal:
---------------------------
| constructor | *function |
| sayName | *function |
| __proto__ | *Object |
---------------------------
Dog:
---------------------------
| bark | *function |
| __proto__ | *Object |
---------------------------
spot:
---------------------------
| name | Spot |
| __proto__ | *Object |
---------------------------
That we can define and use objects in the same way regardless of the underlying inheritance mechanism makes me think that the inheritance mechanism isn’t as significant as we’ve made it out to be. We could architect our programs without knowing or caring exactly how the inheritance will be implemented.
Though, I did say earlier that prototypal can emulate classical plus more. In prototypal, since Animal and Dog are actually objects, that means we can alter and augment them during runtime. We can add new methods to their prototypes that will then be available to all descendant objects. But, to do so would almost certainly be bad practice, for the same reason that to alter or augment the built-in prototypes (Array, Date, etc) is also bad practice – because it can lead to unpredictable behavior. The exact same object could behave differently depending on if, and even when, its prototype is altered. That kind of unpredictability is bad. And if we choose to restrain ourselves from altering prototypes, then the feature set we’re left with is our emulation of classical inheritance.