JavaScript Prototypes and Inheritance

Someone on Hacker News asked for a concise description of "prototype" in JavaScript. I gave it a go, but the topic closed before I could post. So I saved it here for posterity.

And then I blew it out and made it considerable less concise, which might or might not be for the best, but you can be the judge of that. Prototype-based inheritance in JavaScript can be a little bit strange to people accustomed to class-based inheritance, but it really doesn't need to be difficult to grok. Like so many things, once you "get it", it seems easy enough.

Before we begin, let's do a small bit of terminology. In JavaScript, an object's "constructor" is itself an object. For example, you might have seen something like this:

JavaScript function Kitten() { this.fuzzy = true; } var k = new Kitten(); // "Kitten" is k's "constructor"

But if you look, you see that Kitten is an object, right? It's an instanceof a Function , in particular:

js> Kitten instanceof Function true

Since Kitten is an object, it means Kitten can have properties, such as Kitten.prototype .

Just hold onto that for a moment; we'll come back to it.

Here's a tricky one: an object's prototype is an implicit reference to the value of object's constructor's prototype property. That is, the object's prototype is the object pointed to by its constructor's prototype property.

Even saying it two different ways doesn't really seem to take the edge off, does it?

Let's look at the Kitten example, above. In it, k 's constructor is Kitten . Therefore, k 's constructor's prototype property is Kitten.prototype . Therefore, by the above definition, k 's prototype is Kitten.prototype .

Tersely, and only slightly incorrectly: k is-a Kitten , so k 's prototype is Kitten.prototype .

(The "object's prototype" is different than the "object's prototype property"! I know! Remember, the object's prototype is its constructor's prototype property! This is a zany terminology thing that can really get confusing.)

So when you construct an object, that object gets an implicit reference to the value of the constructor's "prototype" property, which is an object:

js> Kitten.prototype [object Object]

Now, by "implicit reference", I mean that it's an internal thing, not some property on your object. In the Kitten example, k.prototype is not the same as Kitten or Kitten.prototype . The implicit reference is hidden away. (It can, however, be gotten with Object.getPrototypeOf() .)

Finally, Here's The Meat: If a property cannot be found in an object, it is searched for in that object's prototype.

That's basically it.

All the rest of it is neato emergent behavior.

Simple example:

JavaScript // here's the constructor: function Weasal() { } // Weasel is to Weasal as Kernel is to KERNAL // set up the prototype object to have some values: Weasal.prototype = { x: 10, y: 20 }; // or you could do this: Weasal.prototype.z = 30; // make a new Weasal object // (this object gets an implicit reference to Weasal.prototype object) var frank = new Weasal(); frank.x == 10; // TRUE -- from Weasal's prototype frank.z == 30; // TRUE -- from Weasal's prototype frank.hasOwnProperty('x'); // FALSE -- it's in Weasal's prototype object // now check this out: frank.x = 30; // assign a value to frank's x property frank.x == 30; // TRUE -- but now we're looking at frank's x! frank.hasOwnProperty('x'); // TRUE -- Weasal's prototype x is hidden // and then: delete frank.x; // blow away frank's x property frank.x == 10; // TRUE -- it's looking at Weasal's prototype again

Here's an example involving making "methods" on an object:

JavaScript function Shark() { }; // constructor Shark.prototype.fireFrickinLaserBeam = function() { doit(); }; fred = new Shark(); // fred doesn't have a fireFrickinLaserBeam property, so it is looked for // in its prototype object (Shark.prototype) and runs from there: fred.fireFrickinLaserBeam();

And here's a more complex example involving inheritance:

JavaScript function Legume() { } Legume.prototype.health = 20; function Bean() { } Bean.prototype = new Legume(); // inheritance! See below for discussion Bean.prototype.audio = true; var b = new Bean(); b.goats = 3490;

Take a look at that code, and see how it compares to the following diagram, which shows the objects and their properties:

Four objects and their properties. The dotted lines represent the implicit connection to the object's prototype (put obtusely, the dotted lines represent the implicit connection to the object referred to by the object's constructor's prototype property.

At this point, b only has the goats property ... so you if reference b.audio , it looks for it in its prototype object ( Bean.prototype )and gets it there, just like in the previous examples.

This is a specific case of our original general rule: if an object doesn't have the property you're looking for, the property is looked for in that object's prototype object. Now for the magic: you might have noticed that the word "object" appears at the beginning and end of that rule... the rule applies to both the object and the object's prototype because the object's prototype is an object.

This means that if the property isn't found in the prototype object, it is looked for in the prototype object's prototype! And turtles all the way down the "prototype chain".

To run the example, then, when we try to evaluate b.health :

Is health in b ? No. So... Since b 's prototype is Bean.prototype , is health in Bean.prototype ? No. So... Since Bean.prototype 's prototype is Legume.prototype , is health in Legume.prototype ? Yes! Bing! We got it!

In this way, you can build an inheritance tree by chaining prototypes to other objects, just like we chained Bean.prototype up to a Legume object. We could make a further subclass Pinto , and set its prototype to a Bean object, for example.

Setting the constructor property

One thing that I didn't mention is that prototype properties of Function s have a constructor property. This property references the function itself, i.e. the constructor. For example:

JavaScript function Beet() { } // constructor Beet.prototype.constructor == Beet; // TRUE // now we can conveniently get the constructor of an object through the // established magic of the prototype chain: var b = new Beet(); b.constructor == Beet; // TRUE, from Beet.prototype

However, this gets hosed when you do inheritance. Here's the breakage, just so you understand it:

JavaScript function Goat() { } // constructor function UltraGoat() { } // constructor UltraGoat.prototype = new Goat(); // inherit from Goat // Goat.prototype is the built-in Function prototype property, which has a // constructor property referring to "Goat": Goat.prototype.hasOwnProperty('constructor'); // TRUE Goat.prototype.constructor == Goat; // TRUE // HOWEVER, UltraGoat's prototype is just a normal object created with new. It // doesn't even have a constructor property in its prototype property: UltraGoat.prototype.hasOwnProperty('constructor'); // FALSE (trouble brewing!) // and so, when you try to get its constructor, it goes up the prototype // chain to Goat.prototype! UltraGoat.prototype.constructor == UltraGoat; // FALSE, Aghh!! UltraGoat.prototype.constructor == Goat; // TRUE, wrong!!

Fortunately there is a quick fix. Now, if you never reference your Ultra-Goat's constructor , this won't be a problem. But nevertheless people commonly patch up the constructor property when inheriting, like so:

JavaScript function Goat() { } // constructor function UltraGoat() { } // constructor UltraGoat.prototype = new Goat(); // inherit from Goat UltraGoat.prototype.constructor = UltraGoat; // fix the constructor property // make some goats: var goat = new Goat(); var ultraGoat = new UltraGoat(); // and now this will work properly: goat.constructor == Goat; // TRUE, as expected ultraGoat.constructor == Goat; // FALSE, as expected ultraGoat.constructor == UltraGoat; // TRUE, as expected

There's more fun to be had with prototypes, but you're a lot of the way there, now.

Share me!

Historic Comments

Comments

Please enable JavaScript to view the comments powered by Disqus.

Disqus