Prototypal Classes and Prototypal Inheritance in JavaScript

© 18 February, 2013, Martin Rinehart

Prerequisite: solid JavaScript object programming (see OP and OOP, above).

It has become common in speaking of JavaScript to discuss its "prototypal inheritance" model. The truth is that the JavaScript prototypes provide a crtitical feature of classical classes and an unimportant (you really don't need it!) feature related to inheritance. Understanding this difference is critical to writing effective, object-oriented JavaScript.

In the classical OOP model, theorists say that "objects combine data and code" which, of course, is misleading if not completely false. In the classical model, object instances carry the data values of object properties. Instance methods are part of the class, where they can be called as needed to serve the instances of the class. It would be extremely inefficient to duplicate instance method code within each object, so this is, sensibly, not done.

In JavaScript, object prototypes serve exactly the same purpose for exactly the same reason. It would be foolish to duplicate instance method code within each object so this is, sensibly, not done. This is the important function of the JavaScript objects' prototypes.

An Example

Assume that you have people with family names and given names. (And assume that you fully expect to add, over time, a full range of prefixes, suffixes, middle names and other complexities, but you're keeping it simple for now.) You can assemble a full name, this way:

function full_name( person ) {
    if ( !person ) { person = this; }
    if ( custom === 'East Asian' ) {
        return person.family_name +
            ' ' + person.given_name;
    } else {
        return person.given_name +
            ' ' + person.family_name;
    }
}

In classical OOP, we would store this function in the class and in whatever manner our language requires, we would make it available to instances of the class so that we could write, for our objects, person.full_name() when we needed a full name.

In JavaScript the above example would be part of a global object named window. We could have asked our constructor to make it a property of individual object instances with a line like this:

this.full_name = window.full_name;

That would, with only minor waste, make the code available to each instance so we could also write person.full_name() when we needed a full name. The waste is that every instance has its own full_name property. We could have avoided that minor waste by placing a reference to the code in our constructor's prototype:

Person.prototype.full_name =
        window.full_name;

The prototype, like the class in OOP, stores properties that are available to all instances. (JavaScript and classical OOP are the same in that "all instances" means the set of objects created from the constructor.) Finally, if we were to really use our prototypes for their class functions, we could have written that code directly into the prototype, avoiding window altogether:

Person.prototype.full_name =
        function () { ... }

All of the above places only a single instance of the code where it will be available for use by all objects created from the constructor. None of the examples above use inheritance.

Introducing Inheritance

Inheritance is the technique used in classical OOP to give one class access to the property names and instance method code of another class. Let's illustrate with a chain. Assume all our people are members of the Baby, Mom or Granny classes, for example. (Yes, we are omitting a sex, to make the example simple. We are also ignoring the very important fact of recursive relationships—Granny was also a Mom and before that a Baby—to keep our discussion on topic.)

Classical OOP

In classical OOP, we would have used an object hierarchy. We could have used Granny as the base class and had Mom inherit from Granny and Baby inherit from Mom. This would have given all three classes family_name and given_name properties and all three would have been able to use Granny's full_name() method.

Inheritance's Goal

As we look at prototypal solutions that achieve the same result, bear in mind that inheritance is not the goal. The goal is to allow the object instances to share a single copy of the method code. Inheritance is only one way of achieving this goal.

JavaScript Solutions

The pre-object solution, (since Fortran, 1957) is still a simple, effective one. We can use a library function.

Library Function

The full_name function as written above, creating window.full_name() uses window as the library (not a good idea, but we'll live with it long enough to illustrate the point). In JavaScript this library function can be called with any object that has the family_name and given_name properties: full_name( granny ) or full_name( baby ), for examples.

There is really no disadvantage to this solution save that it doesn't use our favorite method calling syntax: object.method(). While this is a matter more of taste than engineering, let's look at some ways that allow us to use the object syntax.

Constructor Assignment

First, we could call the library function from a function in the object's prototype, this way:

Granny.prototype.full_name =
    function (} {
        return full_name( this );
    };

That lets us then write granny.full_name() in our favorite, object syntax. We do have to repeat very similar code for the Mom and Baby classes. Alternatively we could use inheritance-like assignments in the other two classes, like this:

Mom.prototype.full_name =
        Granny.prototype.full_name;

Note that these "inheritance-like" assignments do not use the prototype chain. They assign the same reference (probably the memory address) to, in the above example, the Mom that exists in Granny. There will be exactly zero CPU cycles involved in extra lookups.

Prototype Assignment

Of course, we have solved the problem for exactly one method. It is likely that Granny has many methods, all of which might be helpful to Mom. To give each instance of Mom access to all these wonderful methods, we can simply assign her prototype, this way:

Mom.prototype = Granny.prototype;

Again, we have no prototype chain lookups. We will, however, have a problem if we don't want Mom and Granny to be identical.

It Is Not Just full_name()

While the full_name() method may be the same in Granny and Mom, other methods will be similar, but not identical. Not all methods need be shared. Granny may be a whiz at Angry Birds which may not appeal to Mom. To allow differences, we could start Mom's prototype with Granny's capabilities but keep it separate for subsequent modifications. JavaScript's object programming (the ability to manipulate properties' names, as well as their values) comes to the fore, here.

Prototype Replication

We'll start Mom with the same capabilities as Granny and later we'll add and delete from Mom's prototype as needed.

for ( var name in Granny.prototype ) {
    Mom.prototype[name] =
        Granny.prototype[name];
}

// Now, modify Mom.prototype

This achieves our goal of allowing instances of Mom access to the method code in Granny.prototype for full_name() and all other methods in Granny, too. Note that there is no prototype chain here, and calling this "inheritance" would be quite a stretch. It effectively achieves the goals of OOP inheritance.

Mixins

Now, we'll take a quick look at another bit of object programming. Assume that Mom and Granny shared some methods, but each had methods of their own. You want to add the shared methods to Mom and Granny, but not the others. The JavaScript technique is to place all the shared methods in a "mixin" that can be stirred, like broth, into the final stew.

var mixin = {
    method_1: function () { ... },
    method_2: function () { ... },
    ...
};

for ( var name in mixin ) {
    Granny.prototype[name] =
        mixin[name];
    Mom.prototype[name] =
        mixin[name];
}

Just-In-Time Inheritance

In fact, JavaScript's object programming gives you no end of different ways of achieving the same result. Suppose that your full_name() routine was commonly used in Mom objects, but almost never used for Baby objects. Here's a bit of code that will let one Baby "inherit" that method just before it is needed:

// code from some seldom-used place
    if ( baby.full_name === undefined ) {
        baby.full_name = Mom.prototype.full_name;
    }
    ... baby.full_name() ... // Good to go!

Again, note that we haven't used any prototype chain lookup and that this isn't really "inheritance" as used in classical OOP, where one class gains access to another class's full set of methods. It's a way in which JavaScript can take care of the rare need without burdening the rest of the instances of the same constructor.

Conclusion

Oops! We've run out of time and space and we haven't shown a single use of the prototype chain. Well, JavaScript is like that. You make heavy use of prototypes to share instance methods within a class, but little or no use of the prototype chain.

In fact, the only major use of the prototype chain is to add capabilities to fundamental objects, such as Object and adding to Object.prototype is, to say the least, a highly controversial practice. We never change Object.prototype. (You may have noticed the absence of hasOwnProperty() filters.) In fact, we never really use the prototype chain at all. We just go right ahead and use objects.

So the next time you hear someone say or write "prototypal inheritance" please ask them if they actually use the prototype chain. We doubt they do. JavaScript has too many better ways to share access to common routines.


Feedback: MartinRinehart at gmail dot com

# # #