JavaScript prototype inheritance

From Trephine

Jump to: navigation, search
« Why trephine is better for applet development Newline delimited JSON »

[subscribe] Recent blog entries

Live Demos

JavaScript prototype inheritance

In a previous article I mentioned that the JavaScript language does not natively support extending prototypes in the same fashion as classes are extended in a statically typed, classically inherited language such as Java or C#. This is only partly true. In reality, it is possible in JavaScript to override the prototype lookup chain to achieve the same effect - this article shows you how.

First, lets start with the Person prototype - the same example we've been using for other articles in this series:

function Person( name ) {
  this.name = name;
}
Person.prototype.greet = function( otherPerson ) {
  alert( "Hello " + otherPerson.name + ", I'm " + this.name );
}

We can create instances of Person like so:

var bob = new Person( "Bob" );
var alice = new Person( "Alice" );

And we can call methods on these new instances:

bob.greet( alice ); // alerts "Hello Alice, I'm Bob"
alice.greet( bob ); // alerts "Hello Bob, I'm Alice"

Tip: If any of this is unfamiliar to you, I urge you to stop here and revisit the JavaScript concepts of prototype inheritance and the "new" operator.

To review: When we call bob.greet() the JavaScript engine must perform a lookup for the greet property on the bob object to find the function to execute. A property lookup against an instance of Person (such as bob) would check the following places in this order:

  1. The object itself, then
  2. The Person.prototype, and finally
  3. The Object.prototype.

In the case of greet, the engine finds that the bob object does not itself contain such a member, but the Person prototype does (#2). Had we looked up bob.name or bob.toString, the engine would have found them in the object itself (#1) and the Object prototype (#3) respectively.

Alright, enough review - onto the new stuff. Suppose we define a new prototype called Employee like so:

function Employee( name, title ) {
  this.name = name;
  this.title = title;
}
Employee.prototype.status = function( otherPerson ) {
  alert( "Dear " + otherPerson.name + ", I am " + this.name + " the " + this.title );
}

And we remake bob into an instance of Employee:

var bob = new Employee( "Bob", "grunt" );

It should be obvious that we can call bob's status function like this:

bob.status( alice ); // alerts "Dear alice, I am Bob the grunt"

So far so good, except that we want Employee to "inherit from" Person. That is, we'd really like to call bob.greet and have it behave as though bob was an instance of Person. As it stands right now, property lookups on bob check the bob object itself, then the Employee prototype and finally the Object prototype. So how do we inject Person into the inheritance chain?

The answer to this lies in understanding why it is that the Object prototype is checked after the Employee prototype. When the Employee function is defined, the JavaScript engine produces a new Object() and attaches it as the prototype property. It is because the prototype is an instance of Object that Object is checked next in line. The secret to putting Person next in line is to change the type of the Employee.prototype object.

This can be accomplished by setting Employee.prototype to be an instance of Person prior to defining the status method:

Employee.prototype = new Person();
Employee.prototype.status = function( otherPerson ) { /* ... */ }

Now lookups against instances of Employee follow a new chain: first the object itself, then the Employee.prototype, then the Person.prototype and finally the Object.prototype.

One consequence of this action is that performing a lookup on bob.constructor finds Person (rather than Employee) since the old Employee.prototype.constructor was lost when we overwrote Employee.prototype. This is easy enough to fix however:

Employee.prototype.constructor = Employee;

This works well as long as calling new Person() doesn't have any bad side-effects. Such side-effects could include registering the instance with some kind of centralized array, or throwing an exception if the required parameters are not sent.

In our simplistic example, creating a new Person object works out alright, but say our Person function had been defined to throw an exception if called without a "name" like so:

function Person( name ) {
  if (!name) throw "Person cannot be instantiated without a name!";
  this.name = name;
}

We could obviously refactor our Employee prototype override such that it passes a dummy name to new Person, but this is a cat-and-mouse game. Every time the internals of the Person function are modified, there's a chance that any prototype using it in the manner of Employee could break - which is clearly unmaintainable. JavaScript can do better.

It turns out that you can create an object which behaves like it's an instance of Person for the purpose of property lookups, without actually actually creating a new Person instance. This is accomplished by creating a new anonymous function, overriding its "prototype" property, then using a new instance of the anonymous function to override Employee.prototype.

Here's the code:

var anonymous = function(){};
anonymous.prototype = Person.prototype;
Employee.prototype = new anonymous();
Employee.prototype.constructor = Employee;

The above snippet is a bit ugly since we're polluting the scope with that anonymous variable, so the sensible thing to do is to wrap it in an anonymous function closure:

Employee.prototype = (function(){
  var anonymous = function(){};
  anonymous.prototype = Person.prototype;
  return new anonymous();
})();
Employee.prototype.constructor = Employee;

And in the interest of reducing lines of code, we can move the setting of the Employee constructor inside the closure as well:

Employee.prototype = (function(){
  var anonymous = function(){ this.constructor = Employee; };
  anonymous.prototype = Person.prototype;
  return new anonymous();
})();

The above code is a bit dense, even if you've been following along so far, so here's a breakdown of what it does:

  1. Defines an anonymous function and calls it immediately (function(){ ... })()
  2. Inside the function:
    1. Defines an anonymous function which sets this.constructor to Employee when called in a constructor context.
    2. Assigns the anonymous function to a variable called anonymous
    3. Sets the anonymous function's prototype to Person.prototype
    4. Creates a new instance of the anonymous function (which triggers the constructor override) and returns it
  3. Finally, the returned object is assigned to the Employee.prototype.

The final result is the same as before, namely that property lookups against instances of Employee will check against the Person prototype prior to checking the Object prototype.

To satisfy yourself that this is indeed the case, here's an executable summary of the sum of the above snippets:

function Person( name ) {
  if (!name) throw "Person cannot be instantiated without a name!";
  this.name = name;
}
Person.prototype.greet = function( otherPerson ) {
  alert( "Hello " + otherPerson.name + ", I'm " + this.name );
}
function Employee( name, title ) {
  this.name = name;
  this.title = title;
}
Employee.prototype = (function(){
  var anonymous = function(){ this.constructor = Employee; };
  anonymous.prototype = Person.prototype;
  return new anonymous();
})();
Employee.prototype.status = function( otherPerson ) {
  alert( "Dear " + otherPerson.name + ", I am " + this.name + " the " + this.title );
}
var bob = new Employee( "Bob", "grunt" );
var alice = new Person( "Alice" );
bob.greet( alice );   // alerts "Hello Alice, I'm Bob"
bob.status( alice );  // alerts "Dear Alice, I am Bob the grunt"
< execute this code >


And that's about all there is to it. If this helped you understand JavaScript prototype chaining a little better, or if you have any questions, please leave a comment!

Public domain declaration

Just so there's no confusion: all of the code snippets on this page are provided "AS IS", without warranty of any kind, express or implied.

All of the code snippets on this page are hereby released into the public domain by the me, the copyright holder. This applies worldwide. Or in case this is not legally possible: The copyright holder grants any entity the right to use this work for any purpose, without any conditions, unless such conditions are required by law.

If you'd feel better with a "real" license, you're free to use code snippets on this page under the MIT license as described on the about page.

Any links back to this site are always appreciated, but not required. Enjoy!

--Jim R. Wilson (jimbojw) 17:17, 14 April 2009 (UTC)