Yet Another JavaScript Inheritance Implementation

This code has been deprecated – use the new and improved version instead.

I’ve always been a fan of JavaScript – its flexibility makes it
well tailored for its primary execution environment inside of web browsers.
It certainly counts as a language that gets
out
of
your way.

As you probably know, JavaScript is based on prototypes.
What that means in practice is that you can change all existing instances of
an object at runtime, after they’ve been created, by adding or removing methods
from the object’s prototype. You can also update a particular instance of an
object at runtime by manipulating just that object.

This capability is by no mean unique to JavaScript
– Ruby let’s you do the same thing for example – but JavaScript is the most widely
deployed language that has this sort of flexibility. And I believe its the
only widely deployed prototype based language (anyone know of another that
is widely deployed?).

JavaScript Inheritance

Although JavaScript is fully object-oriented, its inheritance mechanism is
clunky. In fact, until the Ajax craze most people didn’t even know that JavaScript
supported inheritance. Just look at the prototype library
– it defines an extend method that copies all the properties/method from one
object to another. Its a great example of JavaScript’s flexibility, but not
a great example of software engineering.

The JavaScript 1.5 guide is
the place to learn how JavaScript’s inheritance works. Its an updated version
of an old Netscape DevEdge article that
the Mozilla Foundation rescued from oblivion. I still
remember reading it back in 2000 – it was quite an eye opener – I hadn’t realized
the power hiding within JavaScript.

Over the years a myriad of implementations have popped up to
simplify JavaScript inheritance. Some of the well known ones are from Douglas
Crockford
, Kevin
Lindsey
, Joshua Gertzen and Dean
Edwards
. These are all fine implementations. They of course
differ somewhat in their capabilities and complexity.

My Take

A while back I put together my own implementation, but never published
it. Since my JavaScript posts seem to be pretty popular, I thought it was time
to open it up for review. Feel free
to use it if you’d like.

I had two main goals in my implementation:

  • Keep it as close to “stan” JavaScript as possible
  • Support multiple levels of inheritance

Its the first goal that distinguishes this implementation from others. In
Douglas’ approach you have to use a specific API to define methods, while in
Joshua’s and Dean’s approaches you have to inherit from a base class and use
a specific API to define methods. For me Kevin’s approach is the cleanest,
but I didn’t come across it until after I had put together my own implementation.

Here is an example of how it looks (a quick aside – in this example I use
the old-school way of defining prototype methods to keep things simple. In
real-code, I generally use the Object.extend
idiom popularized by the Prototype library to add methods to an object – but
not to “” inheritance):

// -- Animal --
function Animal(name)
{
  this.name = name

}

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

}

// -- Pet --
function Pet(owner, name)
{

  this.owner = owner
  this.callInherited(arguments.callee, name)

}
Pet.inherits(Animal)

Pet.prototype.toString = function()

{
  return this.callInherited(arguments.callee) + "\n" + 

         "My owner is " + this.owner
}

// -- Cat --
function Cat(owner, name)

{
  this.callInherited(arguments.callee, owner, name)

}
Cat.inherits(Pet)
 
Cat.prototype.toString = function()

{
  return this.callInherited(arguments.callee) + "\n" + 

        "I eat mice"
}
        
var cat = new Cat('charlie', 'oba')

alert(cat.toString())

The key things to notice are:

  • The inherits method is used to specify that one class inherits
    from another class
  • The callInherited method is used to call the super class
  • You can call up as many levels as needed – in this case Cat.toString calls Pet.toString which
    calls Animal.toString.

The thing I like least about the implementation is the need to pass arguments.callee
as the first parameter to callInherited. But before we can talk about why its
needed, we first need to look at the implementation.

Understanding JavaScript Inheritance

Since there is so much information on the Web about JavaScript inheritance,
I’ll just refer you to the above links if you’d like to brush up on your knowledge.
But as a very quick reminder, the way to say a Pet inherits from an Animal
is like this:.

Pet.prototype = new Animal()

Pet.prototype.constructor = Pet

Note that we have to reset Pet’s prototype’s constructor to point back to
the Pet. This is an ugly wrinkle in JavaScript’s inheritance and is needed
so that instances of Pets know that their constructor is Pet.

Using our example, let’s draw this out:

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

Let’s start with the cat instance. It’s constructor is the Cat function.
Cat’s prototype is an instance of Pet, called pet.
Also notice that cat refers to its prototype, which is pet, through
a hidden pointer. In Mozilla this pointer is exposed via the __proto__ property.
It is not exposed in Internet Explorer. Thus, the best way to get the cat’s
prototype is via this code:

cat.constructor.prototype

Because we had to reset pet’s constructor to point to Cat, we can’t
determine pet’s real constructor, and thus can’t determine its prototype.
The inherits method solves this by saving it in a property called prototype,
as show in the code below.

Function.prototype.inherits = function(parent)

{
  this.prototype = new parent()
  this.prototype.constructor = this

  this.prototype.parent = parent
  ...
}

Implementing callInherited

Onto the tricky bit – how do we implement callInherited? Its
a two step process.

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.callInherited(arguments.callee) + "\n" + 

        "I eat mice"
}

We can use this pointer to look up
the method name like this:

Function.prototype.inherits = function(parent)

{
  ...see above ...
  this.prototype.callInherited = 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[0]
    var args = new Array()

    for (var i=1; i<arguments.length; i++)

      args.push(arguments[i])
    

    // Part I - Figure out where we are in the inheritance
    // hierarchy and while we are at it get the name of

    //the current method.
    var currentConstructor = this.constructor
    var methodName = null 

    
    while (methodName == null && currentConstructor != null)

    {
      methodName = figureMethodName(currentConstructor.prototype, caller)

      currentConstructor = currentConstructor.prototype.parent
    }}
  ...see below ...

}

function figureMethodName(object, method)
{
  for (var key in object)

  {
    var value = object[key]
    if (value === method) return key

  }
  return null
}

In Firefox and Internet Explorer we can don’t have to specify callee as the
first parameter because the support the older, deprecated caller property (see
comments in the code). I’m not sure why caller was deprecated – its obviously
useful. But Opera, Konqueror and Safari don’t support it.

Anyway, the algorithm is that we start at the current object and work our
way up the prototype chain until we find the calling method. Once we find it,
we know the method name and its containing prototype. So in our example, we
have found out that the calling method is named toString
and it is contained on the cat prototype.

The next step is to start at the containing prototype and
search its prototype chain for a method called toString.

      /* Part II - Starting at the current level in the
      inheritance hierarchy work our way upwards until we find
      the next method to call.  We find the method based on its name. */
      while (method == null && currentConstructor != null)

      {
        method = currentConstructor.prototype[methodName]
        currentConstructor = currentConstructor.prototype.parent

      }
      
     // Finally - execute the method      
     return method.apply(this, args)

In our example, we will start our search on the pet prototype looking for
a toString method. We find it and execute it.

Pet.toString in turn calls Animal.toString. So we go through
the whole process again. Starting at cat, we look for the calling method
as specified by caller. Eventually we find it on pet. Next, strarting
at animal, we look once again for a toString method and find it on animal.

What’s Not to Like

As mentioned above, I find having to pass arguments.callee as the first paremter
to callInherited annoying. However, I haven’t figured out a way around it (let
me know if you see one!).

More importantly, looking up the overriden methods is fairly slow. As a result,
there is additional code I haven’t see to cache method lookups so they can
be reused.

So feel free to take a look at the code, and If you have any feedback I’d
love to hear it.

Update 1 – The original code used Prototype’s $A function simply because I forgot to remove it – the code has now been updated.

Update 2 – I added a link to Joshua Gertzen’s implementation.

  1. Krzysztof
    August 22, 2006

    Hi,
    1. Your link to image “http://cfis.savagexi.com/images/prototypes.gif” is broken.
    2. You are using “$A(arguments)” function, which relies on Prototype. I would prefer stand-alone solutions.
    3. Since you are using Prototype, I fear that constructions like “for (var key in object)” may retrieve all non-intrinsic members of Object inserted via Prototype library.
    4. You are blaming Prototype “extend” function as “not a great example of software engineering”, noting in the same time that “In real-code, I generally use the Object.extend idiom popularized by the Prototype library”. Which statement should I regard as valid?
    4. Have you seen recent “http://truecode.blogspot.com/2006/08/object-oriented-super-class-method.html” ? It seems that he dealt the problem with retrieving caller in another way.
    5. I would be happy if someone could finally judge between “myriad of implementations” of inheritance in JavaScript. I’m still trying to it on my own.

    Reply
  2. Charlie Savage –
    August 22, 2006

    Hi Krzystof,

    Thanks for the comments, let’s see if I can answer some of your questions.

    1. “Image broken.”

    Thanks, fixed.

    2. Use of “$A(arguments)” from prototype.

    Agreed – I meant to take that out – will fix.

    3. Since you are using Prototype, I fear that constructions like “for (var key in object)” may retrieve all non-intrinsic members of Object inserted via Prototype library.

    See #2

    4. You are blaming Prototype “extend” function as “not a great example of software engineering”, noting in the same time that “In real-code, I generally use the Object.extend idiom popularized by the Prototype library”. Which statement should I regard as valid?

    Using extend to “fake” inheritance by copying one class into another is bad engineering. Using extend to simply add methods to a class is fine.

    5. Have you seen recent “http://truecode.blogspot.com/2006/08/object-oriented-super-class-method.html” ? It seems that he dealt the problem with retrieving caller in another way.

    Actually, if you look at the last example, his code does user caller.

    6. I would be happy if someone could finally judge between “myriad of implementations” of inheritance in JavaScript. I’m still trying to it on my own.

    Agreed – I think JavaScript itself should be updated to standardize this functionality.

    Reply
  3. vijayaratha@yahoo.co.uk
    November 13, 2008

    hi,
    I’m new to javascript. can you say whether the following line i’m writing is right or wrong?
    var instances_available = InstanceManagementService.listAllInstances();

    document.write(instances_available);

    (‘listAllinstances’ function is in another script. i’m accessing it through another script. But i couldnt get output)

    Thanks.
    Ratha.

    Reply

Leave a Reply

Your email address will not be published.

Top