Sam Elsamman

Nerdy Musings and Other Stuff

JavaScript Inheritance Au Naturel

How many times have you heard that “inheritance is frequently misunderstood even by experienced JavaScript programmers”. I am here to say that after programming JavaScript for 10 years and using inheritance for much of that period I was still confused. So recently I spent some time trying to wrap my head around this to determine what library or pattern I should use for a project. I wanted to share my thoughts while they are fresh in my head to help the next person who researches this topic.

In most mature languages the object semantics are very well defined and incorporated into the language. To say JavaScript is light on object semantics would be an understatement. Influential programmers, including Douglas Crockford and John Resig have written on the subject and provided helpers to improve the situation.  There are also many libraries and frameworks that have support for inheritance. Before delving into them I wanted to see what JavaScript could do on its own, hence Inheritance Au Naturel.

Classical inheritance is commonly based on classes and there are no classes to be found in JavaScript. This stands to reason given that there are no static definitions of attributes.  The standard convention is to “construct” your properties dynamically in a function.  When you invoke your “costructor” function with the new operator, the result will be an object with the properties you initialized.

function Animal (gender, sound) {
	this.gender = gender;
	this.sound = sound;
	this.eukaryotic = true;
}
lion = new Animal(“male”, “roar”);
// lion.gender is “male”
// lion.sound is “roar”
// lion.eukaryotic is true

In order to implement methods, JavaScript provides the prototype property. You can define additional functions that will be referenced from the newly created object by making them properties of the prototype property:

Animal.prototype.hello = function {alert(this.sound)}
animal = new Animal("male", "roar");
animal.hello();  // Pops up roar

Since the property is called prototype you might think that it serves as a prototype for the object and that these functions are copied into the object.  In fact they are referenced in the new object but not copied. This means that if you add additional functions to the prototype even after you create the object they will still be accessible.

You can also set the prototype property such that your function will inherit the prototype of another function. The most common way to do this is to set the prototype to a new instance of another function, whose prototype you wish to inherit.

function Lion (gender) { Animal.call(this, gender, "roar"); }

Lion.prototype = new Animal();

lion = new Lion("male");
lion.hello();

When I first saw Lion.prototype = new Animal(), I scratched my head. Why do I have instantiate an actual Animal object in order to inherit its prototype? To understand why you have to delve into how properties of an object are referenced in the first place. Each time you reference a property using this, JavaScript looks to see if the property belongs to the object itself. If it can’t find I there it looks to see if it can find it in the prototype chain. The prototype chain is formed by the new operator as it creates an object from a function. The new operator does three main things:

  • It creates an empty object
  • It calls the function itself (which will presumably populate the empty object)
  • It establishes a prototype chain (in most implementation it stores a reference to the prototype property in an internal property called __proto__).

It is the __proto__ property that most JavaScript implementations use to find inherited properties.  Since the prototype to __proto__ assignment occurs each time you do a new it forms a chain of ancestor prototypes.  Since __proto__ is an internal property and not even available in all JavaScript implementations, new is the established way to create this chain  (EMCA edition 5 added Object.create as an alternative).

Notice also that the constructor calls the Animal constructor using Animal.call(this, gender, “roar”); Since Javascript does not have a super method (though super is a reserved word), you need to do it in this fashion. The call method will invoke the function so that this will be set to the first parameter.

Another important distinction in Javascript vs. other object oriented languages is that you cannot call other methods assigned via the prototype in your constructor. This is because JavaScript calls your constructor before it creates the prototype chain.

So let’s take a complete example to demonstrate the fundamental inheritance techniques without the use of any helpersThe base class Animal has a hello() method which calls a getType() abstract method which we expect to be overridden when Animal is sub-classed into more specific species. The hello() method outputs some verbiage and can be passed in some additional verbiage..

function Animal (gender, sound) {
        this.gender = gender;
        this.sound = sound;
}

Animal.prototype.hello = function(extra) {
   log(" I am a " + this.gender + " " + this.getType() +
   " and I " + this.sound + (extra ? (" " + extra) : ""));
};

Animal.prototype.sleep = function() { log("z..z..z.."); }

Next we have the Lion which inherits from Animal because it initializes its prototype with an instance of Animal. Its constructor calls the Animal’s constructor. It has a getType() implementation that returns the type of animal.    

function Lion (gender) {
     Animal.call(this, gender, "roar");
}
Lion.prototype = new Animal();
Lion.prototype.getType = function () {return "Lion";}
Lion.prototype.hello = function (extra) {
     Animal.prototype.hello.call(this, (extra ? extra + " " : "")
      + "...but I am still a Lion!");
}

The hello() method in Lion calls the hello() method in the super class, Animal.  This time we use Animal’s prototype property get the hello function. Since Lion is never instantiated as an object, its prototype members are not yet part of the prototype chain you must reference it via the prototype property.

Finally for completeness we add a 3rd level of inheritance with the Lioness with her own implementation of hello() and her overridden getType().

function Lioness () {
    Lion.call(this, "Female");
}
Lioness.prototype = new Lion();
Lioness.prototype.hello = function () {
     Lion.prototype.hello.call(this, "...and playful says the Lion     ess");
}
Lioness.prototype.getType = function () {return "Lioness"};

Now we are ready to instantiate these objects and put them through their paces:

var lioness = new Lioness();
var lion = new Lion("Male");
lioness.hello();
lion.hello();
lion.sleep();

This will output:

I am a Female Lioness and I roar …and playful says the Lioness …but I am still a Lion!
I am a Male Lion and I roar …but I am still a Lion!
z..z..z..

And finally we can confirm that JavaScript’s instanceof operator will correctly report that our hierarchy is correct:

log("lioness instanceof Animal is " + (lioness instanceof Animal))
log("lioness instanceof Lion is " + (lioness instanceof Lion))

If you are wondering how instanceof works given that new returns an object that would otherwise have no connection to the function used to create it, the answer is simple. instanceof works its way up the prototype chain looking for a match.

In conclusion, most people see fit to enhance JavaScript to make inheritance better. My discovery, however, was that better is more form and consistency than actual functionality. For me the key short comings are:

  • Generally awkward constructs that are not obvious (e.g. it is clearer to say Lion.extends(Animal) than Lion.prototype = new Animal()
  • The fact that your constructor will be called as part of the above syntax for inheriting. This means you better code your constructors to not be fussed by having no parameters passed in
  • The lack of a super function makes for inconsistent ways to invoke super class methods (e.g. Animal.call(this, …) in a constructor vs. Animal.prototype.play(…) in other methods.
  • Sub-class methods have to know the name of the super class. As a note when methods want to call their super-class equivalent method, the method does not have exist in the immediate super-class as long as it is somewhere in the chain.

Still doing inheritance au naturel is certainly a viable option. Hopefully this article will make clear exactly what you are getting when you use a library or helper if you choose to do so.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>