technology from back to front

Subclassing in JavaScript, part 2

In my last entry, I described a problem, and promised a solution. There are many solutions online and in various JavaScript toolkits; as far as I can tell this one is the most elegant of the bunch, and I’d be pleased to see it replace the various other solutions out there.

The main goals of this approach are:

* The problem described in the previous post are avoided. What is per-instance stays per-instance, and what is shared stays shared; subclassing doesn’t change one into the other.

* The code to create a subclass looks reasonably elegant and straightforward and not too wordy.

* The result is reasonably efficient in space and time

The approach here also meets one nice but less important goal:

* The approach can be used to subclass from a class not specifically built to work with this approach – in other words, when not subclassing, you make classes in the normal way.

I shan’t include a comparison with any other specific solutions here, but if there are any you’re interested in, ask me.

Code first, explanations second.

// The code to support this approach to subclassing function extend(superclass, constructor_extend, prototype) { var res = function () { superclass.apply(this); constructor_extend.apply(this, arguments); }; var withoutcon = function () {}; withoutcon.prototype = superclass.prototype; res.prototype = new withoutcon(); for (var k in prototype) { res.prototype[k] = prototype[k]; } return res; } // An example of the approach in use // Make a class in the normal way function Parent () { this.array = []; } Parent.prototype = { someMethod: function () { this.array.push(this.array[0]); } } var Child = extend(Parent, function () { this.somethingelse = "hello Mum!"; }, { anotherMethod: function () { this.array.push(this.somethingelse); } }); // And demonstrate that it works properly aa = new Child(); bb = new Child(); aa.array.push("aa"); bb.array.push("bb"); aa.someMethod(); bb.anotherMethod(); print(aa.array); // prints "aa,aa" print(bb.array); // prints "bb,hello Mum!"

So what’s going on? This is almost the same as the traditional “Child.prototype = new Parent()” approach, but with a couple of wrinkles.

We create a constructor function that explicitly calls the no-argument constructor of the parent on the object, then calls the “constructor_extend” function passed in, passing it the constructor arguments. We then set a prototype object on this constructor function.

The class used to create the prototype object is a variant of the Parent class, which is the same except that the constructor is replaced with a no-op. It will have the same \_\_proto\_\_ object as any Parent object, but it won’t have any instance fields set. This means that nothing that was per-object in the Parent becomes shared in the Child.

Once we’ve created this blank prototype object, we assign it as the prototype to our constructor function. We then copy in the methods passed in the “prototype” argument, in order to add methods and other shared things to Child objects that aren’t present in Parent objects. The constructor function is then returned, ready to serve as the new class.

One slight inflexibility with this approach is that the superclass constructor is always called with no arguments. This is often fine, but sometimes you need to be able to pass an argument in. To address this, we break our “extend” function into two parts:

function general_extend(superclass, constructor, prototype) { var withoutcon = function () {}; withoutcon.prototype = superclass.prototype; constructor.prototype = new withoutcon(); for (var k in prototype) { constructor.prototype[k] = prototype[k]; } return constructor; } function extend(superclass, constructor_extend, prototype) { return general_extend(superclass, function () { superclass.apply(this); constructor_extend.apply(this, arguments); }, prototype); }
Now if we want to call the constructor with arguments, we use “general_extend” instead of “extend”:
var Child = general_extend(Parent, function () { Parent.apply(this, ["an argument"]); this.somethingelse = "hello Mum!"; }, { anotherMethod: function () { this.array.push(this.somethingelse); } });
I can’t find a convenient way to implement “super” – if you want to refer to the superclass, for example to defer to the superclass version of an overriden method, you’ll have to do so explicitly. The nearest I can get is this:
var Child = (function (uber) { return general_extend(uber, function() { uber.apply(this, ["an argument"]); this.somethingelse = "hello Mum!"; }, { printState: function() { uber.prototype.printState.apply(this); print("somethingelse:" + this.somethingelse); } }); })(Parent);
but that’s far from pretty. Until we get macros, though, I think this is the cleanest and most flexible way of doing subclassing in JavaScript I’ve seen so far.

Update: I’ve just realised that I have mistaken how the inheritance system presented in KevLinDev works, and in fact the key idea is the same as mine – that of creating a copy of the superclass that has a no-op constructor. Nonetheless I slightly prefer my expression of it, in particular the ease of adding methods and the way the separation between general_extend and extend gives you control over the superclass constructor if you need it. I’m not sure there’s much benefit to the definition of baseConstructor and superClass.

by
Paul Crowley
on
03/08/06
  1. Jeff Watkins
    on 12/09/06 at 9:10 pm

    Paul, it looks like you’ve broken the prototype chain with your extend function. Because the initialiser you return from extend doesn’t directly refer to the “parent” initialiser you won’t be able to use code similar to the following:

    var c= new Child();
    if (c instanceof Parent)
       //  clever processing here

    If you’re looking for a general purpose helper for inheritence you might consider something like the following:

    Function.prototype.extendsClass= function( base )
    {
        this.prototype= new base();
        this.prototype.constructor= this;
    }

    Then you can write your initialisers like the following:

    function Parent( name )
    {
        this.name= name;
    }
    Parent.prototype.sharedArray= [];

    function Child( first, last ) { Parent.call( this, first + " " + last ); } Child.extendsClass( Parent );

    Then you can fully use the instanceof operator.

    Just a thought…

  2. Jeff Watkins
    on 12/09/06 at 9:10 pm

    Damn, formatting is screwed up again.

  3. I’ve just added

    print(aa instanceof Child);
    print(aa instanceof Parent);

    to the end of that code, and it prints

    true
    true

    “A instanceof F” tests if F.prototype appears in the prototype chain for A, which it does in this instance.

  4. no ckomment

  5. Thanks Paul & Jeff for sharing your solutions – had the same original problem, and I eventually used the .call() method and it worked fine. Thanks!

  6. Andrew Sazonov
    on 10/04/07 at 11:27 am

    There is another approach to implement JavaScript inheritance – a “lazy” inheritance which has all benefits of “prototype” based approach like typed classes, but also eliminates necessity to declare external scripts in proper order and automatically resolves and loads (if necessary) dependencies to external scripts that contains related classes.
    This approach is supported by JSINER library

  7. Laziness seems orthogonal to the problem addressed here, no?

 
 


× eight = 56

2000-14 LShift Ltd, 1st Floor, Hoxton Point, 6 Rufus Street, London, N1 6PE, UK+44 (0)20 7729 7060   Contact us