Extending Instance Methods in JavaScript

© 2011, Martin Rinehart

In the previous article we used the JavaScript prototype to provide a toString() method for every instance of a class. We did this by placing a single toString() in the class's prototype. This is the basic idea of "prototypal inheritance" a term that I will immediately discard.

First, we discard the term so that misinformed JavaScripters won't mistakenly think that instance methods are an example of "inher . . .". (No, it's not your fault if you are misinformed. There is a mystery page, noted in this series' navigational headers, that you might want to look up.) An instance method, for those of you who have not tried Java or its class-based relatives, is a method in the class that instances of the class can use. A toString() method is the most common example.

A method that is a property of an object's prototype can be used by that object. It can also be used by that object's siblings—all the other objects instantiated by calling the object's constructor. If A is the constructor for instances of the A class (and you will always follow that convention, right?) methods in A.prototype are available for use by all instances of A—all the objects created by calling ... = new A( ... );.

If you want all instances of Patriots to sing your national anthem when you ask them to sing(), this is all it takes:

Patriot.prototype.sing = function () {
    /* play your national anthem here */
};

Now let's look a little more closely at the prototype chain.

JavaScript's Prototype Chain

If a property of an object is specified, such as object.sing() JavaScript looks first at the object itself. Does it have the specified property? If it finds the property, it uses it. No searching required.

Objects' Prototypes

If an object does not have a specified property, as you probably know, JavaScript looks to that object's "prototype" object. The prototype object is the one specified by the implicit reference the object contains. This reference is one that you cannot read or write. If the object has a constructor, the constructor assigns the implicit reference and it is a reference to the constructor's prototype property.

If the constructor A is used to create the object (... = new A( ... );) the implicit reference is to A.prototype. So A.prototype is the prototype of all instances of A.

prototype Property of Constructors

If you want an entirely different prototype, you can completely replace A.prototype with an object of your own: (A.prototype = some_object_more_to_my_liking;). You cannot, however, get your object instances to use any reference other than A.prototype if they were created using A as the constructor.

Similarly, objects created using JSON object literal notation (... = { name:val, name:val, ... }) are given an implicit reference to Object.prototype and you cannot change this assignment. You can change the contents of Object.prototype but since this effects all objects (yours, your coworkers, the libraries you use, ...) it is considered a very bad practice to do so.

Chaining Prototypes

If a property of an object is not found in its prototype (which implies that it was not found in the object itself), the prototype of the prototype is searched. That object is almost certainly the prototype property of the constructor, but again it is found by following the implicit reference so you are not sure (absent some experimenting).

You saw in our previous page that you could address constructors' prototype methods directly (A.prototype.toString()), which may save JavaScript the trouble of following a chain, as well as saving you the trouble of wondering why a method is not in the chain.

Prototype Methods

By placing a method in the construcotr's prototype property you make it available to all instances created by the constructor. In the case of a toString() method, for example, that is exactly what you want. An alternative is simply to assign the method in the constructor (this.toString = function ...). This is even easier to code, but is very wasteful. It duplicates the method with every instance which is bad practice unless you will have very few instances.

Following the Prototype Chain

You have two choices in creating your own prototype chain (and, in so doing, ensuring that the methods your objects will need actually get found). You can let JavaScript search the prototype chain, or you can insert function references in places that you think they will be needed.

I insert references because the prototype chain depends on "implicit" references. I like magic shows, not magic code.

Duplicating References

Inserting references into prototypes sounds wasteful, until you think about it. Let's insert a reference to a method in class A into the prototype for class Z, where B extends A, C extends B, and so on right on up through X, Y and Z:

Z.prototype.back_in_a =
        A.prototype.back_in_a;

When we call z_inst.back_in_a() the method is found in Z.prototype right after checking z_inst. This is nearly instantaneous. The cost? We have added a reference (probably a four-byte memory address) to the method and an extra name to Z.prototype. A key point is that this is an addition to the Z class, not an addition to each of the instances (there may be lots) of that class.

Adding new references to classes is very cheap and gives you code that is very fast. Circumstances where it is sub-optimal are sufficiently rare so that I don't worry about them. The next example shows a method from class A that has been made available to instances of class C in our previous page's hierarchy:

function A( p1, p2 ) {
    A.init( this, p1, p2 );
}

. . .

A.prototype.add_a_and_b = function () {
     return this.a + this.b;
}

. . .
// C extends B
function C( p1, p2, p3, p4, p5, p6 ) {
    C.init( this, p1, p2, p3, p4, p5, p6 );
}

. . .

 C.prototype.add_a_and_b =
         A.prototype.add_a_and_b;

. . .

var i0 = new A( 1, 2 );
. . .
var i2 = new C( 7, 8, 9, 10, 11, 12 );
. . .
alert( i0.add_a_and_b() + ', ' +
        i2.add_a_and_b() ); // 3, 15

I once wrote a function, inherit() that adds references all along the hierarchy to all extended classes' methods:

/** Inherit from a base class. */
function inherits(
    new_instance, Constructor ) {

  var obj = new Constructor();

  for ( var prop in obj ) {

    if ( typeof(obj[ prop ]) ===
        'function' ) {

      var new_inst = new_instance,
        new_cons = new_inst.constructor,
        new_prot = new_cons.prototyope,
        old_prot = Constructor.prototype;

      if ( ! new_prot[prop] ) {
        new_prot[ prop ] =
            old_prot[ prop ];
      }

    } else { // type !== 'function'
      new_instance[ prop ] = obj[ prop ];
    }

  } // end: for ( var prop in obj )

} // end: inherits()

Let's say we want B to implement all the instance methods of A. You would add inherit() to B's constructor, this way:

// B extends A
function B( p1, p2, p3, p4 ) {
    B.init( this, p1, p2, p3, p4 );
    B.inherit( this, A );
}

In practice, I didn't use inherit() very often. It always seemed better to just use JavaScript to duplicate specific function references. My inherit() did not survive this code gardener's pruning sheers.

And that completes our discussion of intelligent ways for one class to extend another, which should complete this page, but it doesn't. If you are not coming from a Java or other class-based background, you may want to stop here and start coding your own project.

Class Statics

If you never tried Java or its siblings, you never learned that classes didn't just have methods that could support instances, they also could have data of their own and methods that manipulated that data. These are called, confusingly enough, "static" variables and methods. The term "static" means that they are part of the class, not part of instances of the class. The term "class static" is redundant, but I use it anyway as "static" alone reminds me of a type of electricity.

Can we have class statics in JavaScript?

Actually, we met our first class static when we created a constructor cascade. A.init() is a class static. Similarly, A.xxx is a property of the constructor A, named xxx, and can hold data or code, as all JavaScript properties can.

And now you are one project short of mastering classes that extend classes. That project is your own. Look seriously at almost any non-trivial bit of coding and you will see where these techniques can be used. Good luck, and happy JavaScripting.


Feedback: MartinRinehart at gmail dot com

# # #