Prototypal Master Class, Resig

© 2011, Martin Rinehart

John Resig's jQuery has become the leading JavaScript library. (It may or may not be the best one for a particular need, but if you have time to learn just one library, and your goal is a job writing JavaScript, this is the one to learn.) He's also published two books on advanced JavaScripting. In March, 2008, Resig published Simple JavaScript Inheritance to meet roughly the same goals that Flanagan wanted to meet.

Resig apparently wanted his code to be shorter than Flanagan's. Resig claims "clocking in at around 25 lines" but I count just under 60. After we removed the doc and some non-comparables, Flanagan clocks in at just over 40. (Getting an exact count quickly leads to a pointless discussion re counting comments and whitespace. We'll not go there.) Resig is, however, a lot less than half the length of Edwards (next, and last, in this series).

Resig's approach has some surface similarities to Flanagan's approach. For example, both feature the Class class and both Class() methods take a single argument. In Resig's work, the argument is simpler than Flanagan's. It is the prototype object that you want a new class to extend. (If that sounds like Crockford it's because it's like Crockford.)

With Resig's approach you call Class.extend( obj_prototype ) and it returns a constructor. You can pass this constructor back into Class() as _super (Java style) to create a constructor for a class that extends further.

Resig's code is readable (with one huge exception) until you come to his inner loop which, were it not for the fact that an extending class might override an extended class's methods, simply copies from the extended class's prototype to the extending class's prototype. The exception, a var named fnTest, is most unreadable (you don't think a single var could be unreadable?) but not terribly important and it gets its own section below. We'll dive straight into the complex inner loop.

The Inner Loop

In the inner loop, most properties of the extended class's prototype are copied into the extending class's prototype. Look at it with the complexities replaced by highlighted comments (Resig's original comments still in green):

// Copy the properties over onto the new prototype
for (var name in prop) {
    // Check if we're overwriting an existing function
    prototype[name] =
         /* test for duplicate names */  ?

            /* dupe name logic */  :

        prop[name];
}

The new class's prototype gets the incoming class's property. Simple. (I criticized Flanagan for calling his incoming class object data. It wouldn't be fair to give Resig a pass for prop for his incoming class description object. Doesn't class_specs_object tell a reader more?)

Let's gloss over the /* test for duplicate names */, though it is not trivial. Basically, if there is a name in the prototype that matches a name in the incoming class_specs_object there is a potential problem. If both names are function names, and they pass that regex-based is-it-a-function test, then there is really a problem. The trick is to not just copy the extended class's method into the extending class. (That would wipe out, for example, the extended toString() method leaving only the extended class's simpler toString(). What you want is to preserve the extending class's method, but still give access to the extended class's method using the _super syntax.

That said, I'm going to give some broad suggestions of what is going on, but leave the details to you.

Inside the Inner Loop

Within the "we have duplicate functions in extending and extended classes" logic, you have the following overall structure. Again, the complexities are replaced by highlighted comments.

    (function(name, fn){

        /* outer function returns this closure: */

        return function() {

            /* details here */

            return ret;
        };

    })(name, prop[name]) /* outer function called here */

The duplication process is resolved by having an unnamed outer function return an inner function. When an outer function returns an inner function this way, it is returning a closure. The inner function has access to the outer's execution context. In this case, that execution context includes the parameters name and fn. It is in the closure's execution context that the extended functions duplicate method is hidden. Again, I object to this JavaScript "magic" but it is there if you need it.

I invite you to try alternate structures to replace execution contexts whenever possible. In this case, you could attach the extended class's method as a property of the extending class's method, for example. (Since I started using properties attached to functions, I've eliminated most of my closures.)

The Regex-Based, Function-Decompiling Test

Those of you who have avoided regular expressions to this point may want to just skim this. It shows the power (and potential for abuse) of regular expressions.

Resig released this code, but heard from Dean Edwards (author of Base.js and the final master in this series) about the usefulness of this check. Resig added it at Edward's suggestion. (See the comments on Resig's blog.)

First, Resig initializes a fnTest var Edward's way:

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

Let's begin by replacing that trinary with the if statement equivalent:

var fnTest;
if ( expression to be discussed next ) {
    fnTest = /\b_super\b/;
} else {
    fnTest = /.*/;
}

Let's look first at the expression: /xyx/.test( function ... ). This calls the test() method of a regular expression. The test() method in regex.test( string ) returns true if the string contains the regex.

/xyz/ is a very simple "regular expression". It is the characters "xyz", in order. It is similar to the string "xyz" except that it is a regular expression, so you can use it to call regex methods, such as regex.test(). In this case, it asks, "Is 'xyz' found within this string?". You could do exactly the same by string.indexOf( "xyz" ) > -1. But the argument to test() is a string. Here it is called with a function.

The test() method of a regex expects a string argument. On seeing an object (you never forget that a function in JavaScript is an object, do you?) it calls the object's toString() method. So, adding some extra parentheses, we have /xyz/.test( function() { xyz; } ).toString() ). That will convet the argument to a string. It will be something like: function() { xyz; }, a function with the somewhat odd statement xyz; but only if the browser is capable of converting a function to a string.

So this is a weird but, I suspect because Resig's using it, a successful cross-browser way of testing for the ability to look at the code inside a function. At the time Edwards wrote it there were browsers that could not "decompile" (convert back to source code) a function. Such a browser would report false for this test

We are going to use one of two regular expressions, depending on the result of the can-we-decompile-a-function test. The first, /\b_super\b/ is a bit more complex than the second, /.*/. To dispose of the second, "." (a period) in a regex means "any character" and "*" (an asterisk) means "zero or more" so /.*/ is a regex matching zero or more occurences of any character. That matches exactly every character string. Testing (/.*/.test( string )) is true for every possible string. Now back to the /\b_super\b/ regex.

"\b" is a regex metacharacter meaning a "word" boundary. Let's say you want to find "foo" but only if it is a whole word. You would look for /\bfoo\b/. Word boundaries include spaces, most punctuation and the beginning or end of the string being tested. Resig's pattern is /\b_super\b/, which tests for "_super" but only as a whole word. It will not match "_superficial", for example. He uses "_super" as a reference to a function in an extended class when the same function name is used in the extending class. This allows you to access, for example, the toString() method in the extended class even though there is a toString() method in the extending class.

Resig borrows super (without the leading underscore) from Java, where Gosling uses it to refer to methods in the extended class. In JavaScript, super (again, no leading underscore) is an unused reserved word, so Resig avoids a future collision this way.

As I've said before, regular expressions combine great power with near-total unreadability. Use them sparingly.

Example

Application Code

var A = Class.extend(
    {
        init: function ( p1, p2 ) {
            this.a = p1;
            this.b = p2;
        },
        toString: function () {
            return 'A{' +
                'a=' + this.a +
                ',b=' + this.b +
            '}';
        }
    }
);

var B = A.extend(
    {
        init: function ( p1, p2, p3, p4 ) {
            this._super( p1, p2 );
            this.c = p3;
            this.d = p4;
        },
        toString: function () {
            return 'B{' +
                this._super() +
                ',c=' + this.c +
                ',d=' + this.d +
            '}';
        }
    }
);

var C = B.extend(
    {
        init: function ( p1, p2, p3, p4, p5, p6 ) {
            this._super( p1, p2, p3, p4 );
            this.e = p5;
            this.f = p6;
        },
        toString: function () {
            return 'C{' +
                this._super() +
                ',e=' + this.e +
                ',f=' + this.f +
            '}';
        }
    }
);


var o0 = new A( 1, 2 );
var o1 = new B( 1, 2, 3, 4 );
var o2 = new C( 1, 2, 3, 4, 5, 6 );

(In the source for this page, this example is duplicated as live code at the end of the body. Uncomment the alert()s at the very bottom and reload the page to see this run.)

Library Code

/* Simple JavaScript Inheritance
 * By John Resig http://ejohn.org/
 * MIT Licensed.
 */
// Inspired by base2 and Prototype
(function(){
  var initializing = false,
    fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
  // The base Class implementation (does nothing)
  this.Class = function(){};

  // Create a new Class that inherits from this class
  Class.extend = function(prop) {
    var _super = this.prototype;

    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    var prototype = new this();
    initializing = false;

    // Copy the properties over onto the new prototype
    for (var name in prop) {
      // Check if we're overwriting an existing function
      prototype[name] = typeof prop[name] == "function" &&
        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
        (function(name, fn){
          return function() {
            var tmp = this._super;

            // Add a new ._super() method that is the same method
            // but on the super-class
            this._super = _super[name];

            // The method only need to be bound temporarily, so we
            // remove it when we're done executing
            var ret = fn.apply(this, arguments);
            this._super = tmp;

            return ret;
          };
        })(name, prop[name]) :
        prop[name];
    }

    // The dummy class constructor
    function Class() {
      // All construction is actually done in the init method
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }

    // Populate our constructed prototype object
    Class.prototype = prototype;

    // Enforce the constructor to be what we expect
    Class.prototype.constructor = Class;

    // And make this class extendable
    Class.extend = arguments.callee;

    return Class;
  };
})();

Critique

You can learn a lot from studying Resig's code. It is definitely not for beginners.

If you compare Resig's code to the Eich (no library) version, you immediately see that Resig buries the complexities in his Class leaving a very clean interface. If you use the instanceof operator, you'll like the fact that if B extends A, an instance of B is also an instance of A (which is not true using the plain JavaScript method).

Again, I have the same question I had re Flanagan's Class. Is it worth it? I'm sure the answer is "yes" for some.

Moving On

That leaves only one more master in this series. In some ways we have saved the best for last. (Or perhaps we have saved the most thorough, which is not always best.)

Dean Base.js Edwards


Feedback: MartinRinehart at gmail dot com

# # #