OOP in JS, Part 2 : Inheritance

In Part 1 we saw how to create classes in JS, including private, privileged, and public properties and methods. This section discusses inheritance in Javascript.

Summary

Example

To jump right into it, following is a sample showing inheritance between two classes:

function Mammal(name){
	this.name=name;
	this.offspring=[];
}
Mammal.prototype.haveABaby=function(){
	var newBaby=new Mammal("Baby "+this.name);
	this.offspring.push(newBaby);
	return newBaby;
}
Mammal.prototype.toString=function(){
	return '[Mammal "'+this.name+'"]';
}


Cat.prototype = new Mammal();        // Here's where the inheritance occurs
Cat.prototype.constructor=Cat;       // Otherwise instances of Cat would have a constructor of Mammal
function Cat(name){
	this.name=name;
}
Cat.prototype.toString=function(){
	return '[Cat "'+this.name+'"]';
}


var someAnimal = new Mammal('Mr. Biggles');
var myPet = new Cat('Felix');
alert('someAnimal is '+someAnimal);   // results in 'someAnimal is [Mammal "Mr. Biggles"]'
alert('myPet is '+myPet);             // results in 'myPet is [Cat "Felix"]'

myPet.haveABaby();                    // calls a method inherited from Mammal
alert(myPet.offspring.length);        // shows that the cat has one baby now
alert(myPet.offspring[0]);            // results in '[Mammal "Baby Felix"]'

Using the .constructor property

Look at the last line in the above example. The baby of a Cat should be a Cat, right? While the haveABaby() method worked, that method specifically asks to create a new Mammal. While we could make a new haveABaby() method for the Cat subclass like this.offspring.push(new Cat("Baby "+this.name)), it would be better to have the ancestor class make an object of the correct type.

Every object instance in JS has a property named constructor that points to its parent class. For example, someAnimal.constructor==Mammmal is true. Armed with this knowledge, we can remake the haveABaby() method like this:

Mammal.prototype.haveABaby=function(){
	var newBaby=new this.constructor("Baby "+this.name);
	this.offspring.push(newBaby);
	return newBaby;
}
...
myPet.haveABaby();                    // Same as before: calls the method inherited from Mammal
alert(myPet.offspring[0]);            // Now results in '[Cat "Baby Felix"]'

Calling 'super' methods

Let's extend the example now so that when baby kittens are created, they 'mew' right after being born. To do this, we want to write our own custom Cat.prototype.haveABaby() method, which is able to call the original Mammal.prototype.haveABaby() method:

Cat.prototype.haveABaby=function(){
	Mammal.prototype.haveABaby.call(this);
	alert("mew!");
}

The above may look a little bit bizarre. Javascript does not have any sort of 'super' property, which would point to its parent class. Instead, you use the call() method of a Function object, which allows you to run a function using a different object as context for it. If you needed to pass parameters to this function, they would go after the 'this'. For more information on the Function.call() method, see the MSDN docs for call().

Making your own 'super' property

Rather than having to know that Cat inherits from Mammal, and having to type in Mammal.prototype each time you wanted to call an ancestor method, wouldn't it be nice to have your own property of the cat pointing to its ancestor class? Those familiar with other OOP languages may be tempted to call this property 'super', but JS reserves this word for future use. The word 'parent', while used in some DOM items, is free for the JS language itself, so let's call it parent in this example:

Cat.prototype = new Mammal();
Cat.prototype.constructor=Cat;
Cat.prototype.parent = Mammal.prototype;
...
Cat.prototype.haveABaby=function(){
	this.parent.haveABaby.call(this);
	alert("mew!");
}

Spoofing pure virtual classes

Some OOP languages have the concept of a pure virtual class...one which cannot be instantiated itself, but only inherited from. For example, you might have a LivingThing class which Mammal inherited from, but you didn't want someone to be able to make a LivingThing without specifying what type of thing it was.

To be blunt, JS doesn't support this. But, you can make methods of a class which aren't directly exposed to instances of that class. An example of this is the global Math class and its methods; you don't make a new Math() object to use its methods, you just use them.

The following example shows how this could be used to simulate a pure virtual ancestor:

function LivingThing(){}
LivingThing.beBorn=function(){         //Note the lack of the word 'prototype' after the class name
	this.alive=true;
}
...
Mammal.prototype.parent=LivingThing;   //Note the lack of the word 'prototype' at the end
Mammal.prototype.haveABaby=function(){
	this.parent.beBorn.call(this);
	var newBaby=new this.constructor("Baby "+this.name);
	this.offspring.push(newBaby);
	return newBaby;
}

With the above, doing something like var spirit = new LivingThing() would result in an object, but one with no properties and no methods. You would not be able to then do spirit.beBorn(). Further (showing that this is not a 'real' solution), while the method is accessible via this.parent.methodName, the method isn't actually inherited, so attempting to do someAnimal.beBorn() would fail.

Protected methods?

Some OOP languages have the concept of 'protected' methods—methods that exist in a parent or ancestor class that can only be called by descendants of the object (on each other), but not by external objects. These are not supported in JS. If you need such, you will have to write your own framework, ensuring that each class has a 'parent' or some such property, and walking up the tree to find ancestors and checking whether or not the calling object is the same type. Doable, but not enjoyable.

This page copyright ©2003 by Gavin Kistner. Comments, corrections, and criticisms are welcome.