Class-Based Inheritance Implemented in JavaScript

© 26 March, 2013, Martin Rinehart

Prerequisite: solid JavaScript object programming (see OP and OOP, above).

This page leans heavily on the JavaScript convention that a constructor starts with a capital letter, and nothing else does. The function Duck is the constructor for all duck objects. That's too subtle for our "make it readable!" bias, but that's JavaScript.

Note to self: this page is just as bad as everything else you've read re the "magic" commonly recommended. We need some kind of machine that every step of the way shows what happens, visible and otherwise.

Most pages/posts/articles on inheritance and JavaScript show the method we'll explain here. Before we get started, two caveats. First, we don't recommend this method as it is quite unreadable (unless you have read this page, carefully). Second, we don't even recommend the use of inheritance in JavaScript. It is almost always a design failure committed by a JavaScript beginner coming from C++, Java or another class-based OOP background.

JavaScript lets you do object programming (not just object-oriented programming). With object programming there is almost never a need for inheritance. On this site you might start with OP and OOP and continue with OP Examples. (Both are on the menu above.) With object programming skills, you won't need the information in this page (unless you need to read the code you've "inherited" from someone less skilled).

Prototypal Inheritance, Demonstrated

Let's create a simple Animal class from which other, more specific animals will inherit, such as Ducks. Each Animal will have a message specific to the class, and will share the talk() method (talk() calls co(), co() calls console.out(), if available, otherwise alert().) talk() is found in the base Animal class.

This is the base class:

function Animal() {
    this.msg = "Animal: Nothing to say.";
} = function () {
    co( this.msg );

// test:
new Animal().talk();

When you run that much, it will report "Animal: Nothing to say.". The Animal is a base class. It expects you to write a class that inherits the talk() method but provides its own msg. A class like this:

function Duck() {
    this.msg = 'Duck: Quack. Quack. Quack.';
Duck.prototype = new Animal();

// test:
new Duck().talk();

Now you have a Duck class. Instances of Duck have their own msg and inherit the talk() method from the base Animal class.

This could hardly be easier. Unless, of course, you actually wanted some code that a mere mortal could read. There's lots of magic in this line:

Duck.prototype = new Animal();

Before we go on, there are lots of other ways to arrange inheritance. This is pure prototypal inheritance and we doubt that one JavaScripter in ten actually understands it. If you want to know how this magic works, the next section is for you.

Prototypal Inheritance, Explained

You've probably read that JavaScript uses prototypal inheritance. This is sloppy bordering on dead wrong. JavaScript can use prototypal inheritance. We avoid it because the code is unreadable. Cheryl, who will have to maintain our code after we've moved on, cannot read it. If your maintainer cannot read your code, you've written bad code. (Cheryl? She lives here.)

The Prototype Chain

You've probably read about the prototype chain. Let's have a quick review. If an object property is specified (like the talk() method property of a duck object) and the object doesn't have such a property, JavaScript looks for it in the object's prototype. (Note that the object's prototype is not a property named prototype. More on that coming.).

The preceding paragraph can be read, and is processed, recursively. The object's prototype is an object. So if the property is not found in the object's prototype, it is looked for in the prototype of the object's prototype. This recurses back to the granddaddy of all prototypes, Object.prototype. (In practice, this recursion is rarely more than two or three steps long.)

And that tells us what we want: the duck doesn't have a talk() method. The duck's prototype doesn't have one, either. But the prototype of the prototype of the noisy canard knows how to talk. Now, how did that simple line achieve this?

The new Operator

Unfortunately, for those of us who are big fans of readable code, the JavaScrpt new operator does lots of magic (things you can't see). The one's we care about are these.

First, new creates an empty object. Inside the constructor it will be called this. Second, it attaches an implicit property (a property that you cannot read or write) (referred to as "[[property]]" in the ECMAScript specs) to the this object. That property is a reference to the real prototype, which is the constructor's prototype property. There is also an explicit property (regular property that you can read and write) named constructor attached to the constructor function's prototype property. Asking for this.constructor gets a reference to the constructor function. (There is no constructor property for this. Therefore, JavaScript looks in the prototype for this which is this.constructor.prototype, where it finds the constructor property.)

If the new operator is calling the Duck constructor, it's as if there were a line of code that said:

this.constructor = Duck;

Note that Duck, is the function itself. No parentheses. (Parentheses would call the function, returning the function's result.)

More exactly, there is no constructor property for the this object. So the property is looked for in this's prototype, which is "[[prototype]]" (the one you can't read) that refers to Duck.prototype, a regular property that you can read and write.

The last bit of magic that new performs, when your constructor has run to completion, is to reach inside the constructor, grab this and yank it right out of the function, passing it along to the left.

Assignment to the Prototype

If you are unclear on any of the steps of the new operator, reread the above. Slowly would be smart.

Now you see what happens, mostly. The one thing that we haven't covered is another bit of magic. When JavaScript finds a duck trying to talk() (or when it sees any property that is not part of the duck object) it looks to the duck's prototype, which is not a property named prototype. It is the implicit property that the specs call "[[prototype]]". (Those brackets are not the code brackets that delimit arrays. They are special syntax in the specs that say, "You can't read or write this property.")

So now you know that every object has a prototype and it is available to your code, for a Duck, as duck.constructor.prototype and it is available to your JavaScipt engines as the duck's implicit "[[prototype]]" property.

So now let's work this through, one step at a time:

// magic line:
Duck.prototype = new Animal();

You know that Duck is the constructor for each duck object. After the new operator it will be available (by lookup in the prototype) as the constructor property:

duck.constructor // reference to Duck

We know that an object's constructor.prototype is the object's [[prototype]]. So we are assigning to the prototype of our duck object, the one that JavaScript will be looking to for properties it needs to find.

We know that the new operator, when we call new Animal(), has created a this object and that:

this.constructor === Animal; // by prototype lookup

Since the this object's "[[prototype]]" is Animal.prototype we have assigned two things: the anonymous Animal's msg property and the anonymous Animal's prototype, which is Animal.prototype. The latter has a single method, talk(). Since the duck has been given its own msg property, the new msg inherited from the Animal is overridden.

However, the talk() method in the Animal.prototype is the only one available in the prototype chain. Follow slowly:

duck, an object created by calling new Duck(), received a msg from the Duck constructor. It also received a new Animal() in its own constructor. The new Animal() has a msg but JavaScript will ignore it. The duck.msg comes at the very head of the prototype chain. But what is important is the talk() function in the Animal's prototype. This is the prototype of the duck's prototype so it will provide the talk() method since it is the first (and only) one in the prototype chain.

To repeat, there is no direct method, so JavaScript will look for one in the duck's prototype. The duck's prototype is Duck.prototype. That also has no talk() method so Duck.prototype's prototype will be searched. That is Animal.prototype where the talk() method is found.

Alternatives to Prototypal Inheritance

Is there a simpler way to get the result we want? The answer is yes. But to find the answers to questions like that, you have to focus closely on defining the result you want. (Road maps are more helpful if you have a destination.)

In our example, we inherited from the base class. This was not the goal, however. We wanted to use the method talk() for all of our Ducks (and, one assumes, for Dogs and Donkeys, too). Accessing the base class method is our goal. Let's try to get this job done.

Method 1

We could have just made the change highlighted in this listing:

function Duck() {
	this.msg = 'Duck: Quack. Quack. Quack.';
Duck.prototype = Animal.prototype;

// test:
new Duck().talk();

Instead of creating a new object and using it as a prototype, we just make another reference to the prototype object. Now we have both Animal.prototype and Duck.prototype pointing to the same object. It has one method: talk(). This would work just as well if we were sharing a hundred methods, too. In addition to its simplicity, it also avoids the lookup from the Duck to the Animal class, saving time (probably negligible, but these savings mount up) on each direct access from Duck to the base class.

Disadvantage? We don't copy the msg data property into the Duck prototype. In our example that's no problem. We didn't use the msg in the prototype. We used the duck's own msg. But if we had a large collection of data properties in the base class, this would be an issue.

We wouldn't care to argue against someone who said that this isn't really inheritance. But that's why we pointed out that our goal was to share a method, not to use a particular technique for sharing methods.

Method Two

Here we show a more radical departure. We've rewritten the library routine, co() and the object instance method talk() as a single library routine. It takes, as arguments, the message and the object calling the routine. (That is commonly needed to convert an object routine into a library routine. In this case, we have no immediate use for the calling object.)

function talk( talker, msg ) {
	if ( console && console.out ) {
		console.out( msg );
	} else { alert( msg ); }

function Animal() {
	this.msg = "Animal: Nothing to say.";
} = function () {
	talk( this, this.msg );

The advantage of the library routine is the absense of a this magic reference. (In JavaScript, this is a common source of trouble.) This is a particularly helpful technique in reverse: when you are converting code from library-based functions to object-based methods.

Method Three

Like Method Two, only this time convert the Duck prototype to call the library routine directly, instead of just borrowing the base class prototype. The obvious disadvantage is that you repeat the calling code in both classes. (Possibly, when you get to adding dogs and donkeys, and a hundred others, this may be a serious isse.) Whether this helps or hurts depends on future directions. Will all the other fish and fowl on the farm share the same method, or will you need to customize the method a bit for the different types of things that talk(). If your destination is separate routines, starting with separate routines may make sense.

Method Four

A "mixin" is now a JavaScript classic for sharing functionality without inheritance. You'll need to look this one up when you are next tempted to fall back on an inheritance-based design.

More Methods

The beauty of object programming is that it lets you have so much freedom in structuring the solution you want. We could place the talk() in the Duck's prototype and let all the other animals access it there. (In this example that would be a bit strange. Did we want the ducks to say "Moo!"? We have seen examples in practice where this makes sense, however.) Really, the only limit is your creativity, which is why so many JavaScripters are in love with the language.

Feedback: MartinRinehart at gmail dot com

# # #