March 20, 2011

Understanding John Resig's 'Simple JavaScript Inheritance'

John Resig created a script for simulating class like inheritance in JavaScript. It is inspired by the approach used by base2 and PrototypeJS. He coined it "Simple JavaScript Inheritance" and the result uses some clever techniques for achieving features like super methods.

Read the original blog post for full details on the script. It's also discussed in his book "Secrets of a JavaScript Ninja", which includes a slightly different variant. The book version adds a subClass method to Object, rather than creating a global variable.

Original Script - John Resig Simple JavaScript Inheritance

Below is the original version of the code, with comments removed for brevity.

(function(){
    var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
    this.Class = function(){};
    Class.extend = function(prop) {
        var _super = this.prototype;
        initializing = true;
        var prototype = new this();
        initializing = false;
        for (var name in prop) {
            prototype[name] = typeof prop[name] == "function" &&
                typeof _super[name] == "function" && fnTest.test(prop[name]) ?
                (function(name, fn){
                    return function() {
                        var tmp = this._super;
                        this._super = _super[name];
                        var ret = fn.apply(this, arguments);
                        this._super = tmp;
                        return ret;
                    };
                })(name, prop[name]) :
                prop[name];
        }
        function Class() {
            if ( !initializing && this.init )
                this.init.apply(this, arguments);
        }
        Class.prototype = prototype;
        Class.prototype.constructor = Class;
        Class.extend = arguments.callee;
        return Class;
    };
})();

Breakdown of the Simple Inheritance script

What follows is a breakdown of how this script is implemented and the techniques used.

(function(){
    // ...
})();

The first thing we have is a immediately invoked function expression, to create a scope wrapper around the code.

var initializing = false,

The initializing variable is fairly straight forward. It's a boolean that is checked when the Class constructor function (covered later) is invoked. Setting initializing to true or false flags whether or not we are creating an actual instance, or only assigning an object result to the current prototype object for inheritance purposes.

If we are creating an instance (initializing == false), and the Class has an init() method, then init is automatically fired as a convenience. Alternatively, if we are only doing prototype assignment for inheritance (initializing == true), nothing special is done and init is not fired. This ensures that there are no unwanted side effects caused by each constructors init() function firing when any inheritance is done via var prototype = new this();.

fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;

The purpose of fnTest is to to establish a regex pattern to use when detecting _super() calls inside class methods. It utilises a technique called function decompilation, also known as function serialisation. Function serialisation occurs when a function definition is converted to a string, which most modern browsers support via the toString method.

To test function serialisation, fnTest uses a simple anonymous function function(){xyz;} which is set up with the contents "xyz", and uses a regex test for "xyz" in the toString output. This will return true for browsers that support function serialisation, since the function definition will become a string, containing "xyz" as part of it's string contents. In that case, fnTest will be assigned the result /\b_super\b/, which is a regex pattern that checks for _super (inside word boundaries), otherwise, fnTest will be assigned the catch all .*, which will always return true when tested, for browsers that don't support function serialisation.

Using the resulting fnTest regex, and the function serialisation technique, we can easily detect if class methods contain calls to _super. If they do, special handling can be applied, otherwise the methods can be treated normally. This helps avoid extra processing for methods that exist in both the new class and the parent class that don't have _super calls, since those are just straight method replacements and don't interact.

Browsers that don't support function serialisation will always return true, and apply the extra processing for _super handling, even if the new method doesn't use _super. It's a small performance inefficiency, but it means those browsers will always work correctly.

this.Class = function(){};

Create an empty constructor function and assign it to the global variable Class. This will be the basis of the root constructor. It has no defined contents, or a prototype object, other than the initial extend method created below. this refers to the window object, making Class a global function.

Class.extend = function(prop) {
    // ...
}

Add an extend method that takes a single parameter prop that is an object of properties and methods. This object will form the basis of the new constructors prototype object and will be mixed in with the prototype object result of the parent constructor.

var _super = this.prototype;

Grab the "parent class", or in JavaScript terms, the prototype object of the current object, and store it in _super. this.prototype is a reference to the prototype object (the properties and methods) of the object being extended. It allows the current logic to access the super methods of the parent where needed. The variable is called _super (as opposed to super), because super is a reserved word in JavaScript, even though it currently has no applied purpose in the language.

initializing = true;
var prototype = new this();
initializing = false;

Instantiate a base class and store it in a variable called prototype, but only create the instance, don't run the init constructor. This is achieved by setting initializing to true, so that the Class function doesn't fire init() when new this() is called. After prototype is assigned, initializing is set back to false, so any further call would work as normal (eg. when we actually want to create a real instance).

for (var name in prop) {
    // ...
}

Using a for loop, we iterate over the properties and methods contained in the prop object, which was passed in to the extend method. With the exception of some special handling for _super, we will copy (mixin) all the items to the prototype variable.

prototype[name] = typeof prop[name] == "function" &&
    typeof _super[name] == "function" && fnTest.test(prop[name]) ?
    (function(name, fn){
        return function() {
            // special handling for _super
        };
    })(name, prop[name]) :
    prop[name];

When we loop through the items contained in prop, if the item being copied is a method (typeof prop[name] == "function"), and also exists as a method on the parent (typeof _super[name] == "function", aka typeof this.prototype[name] == "function"), and contains a call to _super (fnTest.test(prop[name]) == true), we augment the method by composing a new method that handles calling the parent (super) method as well as the original method.

For the purposes of clarity, let's adjust the above code to use more explicit syntax, as the ternary makes it a little cryptic to see whats happening at first glance. Here's the same code, using an if / else statement.

if (typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name])) {
    prototype[name] = (function(name, fn){
        return function() {
            // special handling for _super
        };
    })(name, prop[name]);
} else {
    // just copy the property
    prototype[name] = prop[name];
}

Another immediately invoked function expression wraps the special handling for super to trap the name and prop[name] values. Without this closure, the variable references would be wrong when the returned functions are called (eg. they would all end up pointing to the last name, prop[name] entries in the loop).

For each item, we return a new function that handles the original method (via _super) as well as the new method.

// special handling for super
var tmp = this._super;
this._super = _super[name];
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;

For the special handling of super, the first thing we do is store any existing _super property that may exist as part of the class creation parameters, in a temporary variable called tmp. This protects against accidentally overwriting an actual property named _super if one exists. When we're done, we'll return the tmp value back to this._super so it can be accessed again normally.

The next thing we do is take the parent _super[name] method (aka this.prototype[name]) and copy it to the current object as this._super. When fn is invoked (using apply), this._super() will be a local reference to the parent method, and all this references in the parent method will refer to the current object.

Finally, any return value from the method is stored in ret and then returned from the function after we set the original _super value back.

Here's a simple demo to illustrate the results of these actions. Given a simple class definition Foo, that we extend to create Bar:

var Foo = Class.extend({
    qux: function() {
        return "Foo.qux";
    }
});

var Bar = Foo.extend({
    qux: function() {
        return "Bar.qux, " + this._super();
    }
});

When Foo.extend is called above, due to the presence of this._super in the qux method, the original Bar.prototype.qux implementation is changed by the script to effectively become:

Bar.prototype.qux = function () {
    var tmp = this._super;
    this._super = Foo.prototype.qux;
    var ret = (function() {
        return "Bar.qux, " + this._super();
    }).apply(this, arguments);
    this._super = tmp;
    return ret;
}

The final step in the script, is to define and return the constructor function that will be called when actual object instances are created.

function Class() {
    if ( !initializing && this.init )
        this.init.apply(this, arguments);
}

This code creates a new constructor function called Class, which is different from this.Class created earlier, as it's local to Class.extend. The constructor will be returned from calls to Class.extend (and derivatives such as Foo.extend). This is the constructor that will be invoked when actual object instances are created by calls to new Foo().

As a convenience, the constructor will automatically fire an init() method if one exists. As mentioned earlier, the initializing variable ensures that init() is not called when doing inheritance, only when creating instances.

Class.prototype = prototype;

The final prototype variable, is now a mixin of the parents prototype object returned from instantiating the parent constructor function (eg. var prototype = new this()) and the result of the for loop that iterated through the object passed to the extend() function call.

Class.prototype.constructor = Class;

Since we are overwriting the entire prototype object, store the original constructor in the constructor property so it retains the default behaviour of being able to access the constructor function from an instance.

Class.extend = arguments.callee;

Give the constructor it's own extend method, via arguments.callee, which in this case refers the original Class.extend function. We could avoid the use of arguments.callee if we named the original extend function (eg. Class.extend = function extend(prop)). The assignment could then be done using Class.extend = extend;.

return Class;

The returned constructor function is now set up with a prototype object, constructor property, an extend method and auto init() firing on instantiation.