New and Improved JavaScript Inheritance

Last time I talked about my implementation of JavaScript inheritance. Since then I’ve made a number of improvements – its now simpler, faster and easier to use.

In my post I pointed out some alternative, including one from Kevin Lindsey. Kevin’s implementation is my favorite because its the simplest and least intrusive solution.

It turns out our two implementation are quite similar – the main difference is that I added in a a callSuper method. This might not seem like much, but let’s look at an  example to see how is dramatically changes the code you write.

Let’s assume we have a Pet object that inherits from a Animal object (I don’t use “class” since JavaScript doesn’t have classes, just prototypes).

Here is how you would code in using Kevin’s implementation:

function Pet(owner, name)
{
  this.owner = owner
	 Pet.baseConstructor.call(this, owner, name)
}
KevLinDev.extend(Pet, Animal)

Pet.prototype.toString = function()
{
  return Pet.superClass.toString.call(this) + "\n" + 
         "My owner is " + this.owner
}

Notice three things. First, the name of the current class is hard-coded into methods (for example, look at Pet.baseConstructor). Second, calling an overridden constructor (Pet.baseConstructor for example) is different than calling an overridden method (Pet.superClass). Last, invoking a super method requires using JavaScript’s call function instead of a normal method call.

Now let’s add in a callSuper method and see what the code looks like:

function Pet(owner, name)
{
  this.owner = owner
  this.callSuper(arguments.callee, name)
}
Pet.inherits(Animal)

Pet.prototype.toString = function()
{
  return this.callSuper(arguments.callee) + "\n" + 
         "My owner is " + this.owner
}

We’ve gotten rid of all the limitations mentioned above – but have added two.

This first is the need to pass arguments.callee as the first parameter. This is used by callSuper to determine the currently executing method. Without this information, callSuper cannot lookup the overridden super method.

In previous versions of JavaScript you don’t need this parameter because you could determine what method invoked a method by arguments.callee.caller. However, caller has been deprecated in JavaScript. Thus this works in Firefox and Internet Explorer but not Safari, Konqueror, and if I remember correctly, Opera.

The second limitation is hidden – using callSuper introduces a performance hit since it has to look up the calling method (a consequence of caller being deprecated). However, in my new improved implementation this performance hit has been eliminated for some common cases and in other cases it has been reduced by 50%. Thus it only comes into play if you’re creating hundreds and hundreds of objects.

Updated Implementation

So let’s look at the new implementation:

/* Copyright (c) 2006 Charlie Savage 
License: BSD:
http://www.opensource.org/licenses/bsd-license.php
*/

Function.prototype.inherits = function(superConstructor)
{
  /* Borrowed from Kevin Lindsey - create a dummy
     constructor to create prototypes.  This is useful
     because it means that we don't have to modify
     the real constructors to have if-statements to 
     guard against initialization if we are creating
     a prototype (versus a regular instance).*/
  function CreatePrototype() {}
  CreatePrototype.prototype = superConstructor.prototype
  
  /* Reset the current constructor. This is an ugly hack required 
     by the JavaScript prototype-chain implementation. */
  this.prototype = new CreatePrototype()
  this.prototype.constructor = this
  
  // Save a reference to the superClass constructor
  this.superConstructor = superConstructor

  this.prototype.callSuper = function()
  {
    /* In IE and Firefox we can get the caller argument via
       arguments.callee.caller.  However, for some reason this
       has been deprecated in JavaScript and is not supported
       by Safari or Opera.  So we have to pass it in as
       a parameter.  Yuck.*/
    // var caller = arguments.callee.caller
    var caller = arguments[0]
    var args = new Array()
    for (var i=1; i<arguments.length; i++)
      args.push(arguments[i])
    
     /* Figure out where we are in the inheritance hierarchy and
       the name of the method that was invoked. */
    var currentConstructor = this.constructor
    var methodName = null 
    
    while (methodName == null && currentConstructor != null)
    {
      // Shortcut - Is a constructor being called?
      if (caller === currentConstructor)
        return currentConstructor.superConstructor.apply(this, args)
  
      methodName = figureMethodName(currentConstructor.prototype, caller)
      currentConstructor = currentConstructor.superConstructor
    }
    
    if (!methodName)
      throw new Error("Could not find method: " + methodName + ".")

    if (!currentConstructor)
      throw new Error("Prototype does not have an ancestor: "
       + currentConstructor + ".")
      
    // Finally - execute the method     
    return currentConstructor.prototype[methodName].apply(this, args)
  }    
}

function figureMethodName(prototype, method)
{
  for (var key in prototype)
  {
    if (prototype.hasOwnProperty(key) &&
        prototype[key] === method)
      return key
  }
  return null
}

And here is an example of using it:

// -- Animal --
function Animal(name)
{
  if (!name)
    throw new Error('Must specify an animal name')
  this.name = name
}

Animal.prototype.toString = function()
{
  return 'My name is ' + this.name
}

// -- Pet --
function Pet(owner, name)
{
  this.owner = owner
  this.callSuper(arguments.callee, name)
}
Pet.inherits(Animal)

Pet.prototype.toString = function()
{
  return this.callSuper(arguments.callee) + "\n" + 
         "My owner is " + this.owner
}

// -- Cat --
function Cat(owner, name)
{
  this.callSuper(arguments.callee, owner, name)
}
Cat.inherits(Pet)
 
Cat.prototype.toString = function()
{
  return this.callSuper(arguments.callee) + '\n' + 
        'I eat mice'
}
        
var cat = new Cat('charlie', 'oba')
alert(cat.toString())

CreatePrototype

The first thing that might look suspicious is CreatePrototype. This is a brilliant idea I borrowed from Kevin. It neatly solves a common problem that bites all new JavaScript developers – and sometimes experienced ones who are forgetfully like yours truly!

Take a look at the Animal constructor:

function Animal(name)
{
  if (!name)
    throw new Error('Must specify an animal name')
  this.name = name
}

In traditional JavaScript inheritance, to have Pet inherit from Animal, you’d write the following code:

Pet.prototype = new Animal()
Pet.prototype.constructor = Pet

When we try to create a new Animal as a prototype, the Animal constructor will throw an exception because it was not passed a name. To avoid this, you have to sprinkle ugly if-statements throughout your constructors to protect code that should only be called when creating a new instance versus prototypes.

CreatePrototype solves the whole mess because it entirely skips calling the Animal constructor. Instead, new CreatePrototype() creates a blank new object. However, that object’s __proto__ property is set to the Animal’s prototype thereby inheriting all its methods which is exactly what we want.

Let’s look at a diagram to see what this looks like:

The gray boxes with capital letters are constructors (JavaScript functions)
while the blue objects with underlined letters are instance of objects.

By using CreatePrototype(), we replace animal with createPrototype which is nothing more than a blank object. But notice that the __proto__ properties are still correctly setup.

callSuper

So how does callSuper work?

First, we need to determine the name of the method being currently executed and what prototype contains it. Let’s use Cat.toString as an example – its defined as an anonymous function so it doesn’t actually have a name. Thus the need for the ugly callee parameter, which is pointer to the currently executing method:

Cat.prototype.toString = function()
{
  return this.callSuper(arguments.callee) + "\n" + "I eat mice"
}

Starting at the Cat prototype we climb up the inheritance tree until we find the calling method. To do this, we call figureMethodName for each prototype, specifying the current prototype and method (i.e., caller).

function figureMethodName(prototype, method)
{
  for (var key in prototype)
  {
    if (prototype.hasOwnProperty(key) &&
        prototype[key] === method)
      return key
  }
  return null
}

The call to hasOwnProperty is quite important – we only want to return methods defined on the current prototype. If we return a method defined by a super class, then we’ll end up with infinite recursion. That happens because we’ll end up calling the method on a child prototype instead of the super prototype. Thus, when callSuper is invoked again we’ll end up back in the same place (remember that each time we invoke callSuper we start at the bottom of the inheritance chain because this always refers to the current JavaScript object).

Once we’ve found the method name, then we simply invoke it on the superConstructor and let JavaScript do the rest.

Here is the updated code – and as usual all feedback is welcome.

Leave a Reply

Your email address will not be published.

Top