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
callsPet.toString
which
callsAnimal.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.