Reflections on Prototypical Inheritance
JavaScript is an object-oriented language. It uses prototypical inheritance instead of the more common class based inheritance, but luckily it—being the flexible language it is—allows one to utilize both methods of inheritance. But why even allow for both when you usually work with class based models? Well, I can’t speak for the people responsible for making that decision, but I will give you some of my thoughts on the subject.
Now then, what is prototypical inheritance? Douglas Crockford described it so well when he tried to defend it as a full-fledged OO paradigm: “Objects inherit from other objects, what could possibly be more object-oriented than that?”. So that’s it, you don’t create a new object by instantiating a class, you instantiate objects from other objects. By the way, don’t make the mistake of relating prototypical inheritance with the Prototype design pattern, they are very different beasts.
Unfortunately the syntax JS provides for this isn’t very straight-forward. You have to first create a constructor, associate it with the object you want to inherit from (called the prototype) and then instantiate through the constructor. Here’s an example
function Person (name, eyeColor) { this.name = name; if (eyeColor) { this.eyeColor = eyeColor; } } Person.prototype = { age : 0, eyeColor : "Blue", father : null, mother : null }; var adam = new Person("Adam", "Green");
Okay, seeing this doesn’t really distinguish it from class based programming, and this is indeed how you would simulate classes.
I didn’t choose people as an example arbitrarily, I think it can demonstrate an important aspect of prototypical inheritance. Since we want to model reality, let’s look at people this way. The class based solution would probably be to create a Person class and we might provide a constructor for creating a child of two parents. But, do aspiring parents consult some abstract template of a human in order to have children?
For simplicity’s sake, let’s assume that all children inherit traits such as eye color from their father. Here’s a class based solution:
// This would act like a "class method" since it's placed directly on // the constructor. Person.create = function (name, mother, father) { var child = new Person(name); child.eyeColor = father.eyeColor; child.mother = mother; child.father = father; return child; }
Here’s the same example using prototypes, letting the child inherit more explicitly from it’s father, it also shows that the setup code get’s fairly awkward with the provided interface (it can be abstracted somewhat).
// Adding an instance method after the prototype has been defined. Person.prototype.createChild = function () { function DummyConstructor(father, mother) { this.father = father; this.mother = mother; } DummyConstructor.prototype = this; return new DummyConstructor (this, this.spouse); };
It ended up about the same size as the class based version, but there
is one important difference, where did we set the child’s eye color?
It’s of course inherited from the father (the prototype). This
property is not copied to the child, rather once we try to evaluate
child.eyeColor the request will propagate up the
“prototype chain” and the father’s eye color will be returned. We can
later modify the eye color of the parent and the change will be
reflected on the child. We can also set the eye color on the child,
which won’t modify the parent, and later on we can delete the property
so we once again inherit the eye color. Basically, once we set a value
it is set on the outermost object in the chain, but resolving values
may cause the aforementioned propagation on the prototype chain. This
also means that a minimal amount storage is used when a new object is
created, but that might be a later article of mine.
How useful is this? With this example it’s not obviously great and I might update once I think of a relationship that’s more natural to express with prototypes. I imagine there are cases where prototypes would be a perfect fit, and if that’s the case, why would you want to omit them from the language? On the other hand, uses might be such a rare occurance that it can be implemented in the language when the need arises.
Post new comment