Monthly Archives: March 2021

JavaScript Pattern For Deferred/Just-In-Time Subclass Prototype Initialization

Impetus And Use Case

The impetus for this code pattern is to be able to support class hierarchies spanning multiple “async defer“-loaded JavaScript files.

Developing A Solution

A typical SubClass.prototype = new SuperClass or Object.create(SuperClass) won’t work because a super-class may not have finished loading when a subclass is defined.

To avoid order-of-execution issues, just-in-time initialization of the prototype is performed upon the first constructor invocation. The prototype of the default new instance is already bound by the time the constructor function executes, so the constructor function must return a new “new” instance after switching prototypes.

The constructor calls a helper class-method, $c, to perform the just-in-time initialization. This method replaces itself during initialization to prevent reinitialization in subsequent calls.

Both versions of the helper method call a second helper method, $i, to (potentially) perform instance initialization. This method is registered as both an instance method (for polymorphic invocation) and a class method (as a shortcut for prototype.$i, to facilitate super-initialization in subclasses).

To prevent any initialization of instances for subclass prototypes and duplicate initialization of replacement new objects, the constructor accepts its class object as a sentinel value to indicate that no initialization should be applied.

When the sentinel value is supplied to the constructor, the single parameter false is passed from the constructor to $c and from $c to $i. Otherwise, the constructor’s arguments object is passed as the only parameter instead.

Sample Pseudo-Trace

Here’s a simplified view of what the flow of execution might look like creating the first instance of a subclass using a previously initialized super-class for its prototype.

instance = new Sub(...parameters) // Initial super is Object
  Sub.$c_1x(Arguments [...parameters])
    Sub.prototype = new Super(Super)
      Super.$c(false)
        Super.$i(false)
    new Sub(Sub) // New super is Super
      Sub.$c(false)
        Sub.$i(false)
          Super.$i(false)
    Sub.$i(Arguments [...parameters])
      Super.$i(Arguments [...parameters])

Code Pattern

function Class () {
    var c = Class;
    return c.$c.call(this, arguments[0] !== c && arguments);
}
Class.$c = function (args) { // JIT 1x class init helper
    // var c = Class, p = c.prototype; // "Base" classes (Object subclasses)
    var s = SuperClass, c = Class, p = c.prototype = new s(s); // Subclasses
    p.constructor = c; // Subclasses
    c.$c = function (args) { return this.$i(args); }; // Post-init helper
    p.$i = c.$i = function (args) { // Instance init helper
        s.$i.call(this, args); // Subclasses
        if (!args) return this; // Skip init on false
        // Add this.properties here
        return this;
    };
    // Add p.methods here
    // return this.$i(args); // Base classes (original prototype)
    /*
     * We need to return a new "new" to pick up the new subclass prototype.
     * Note that new c(c) invokes $c(false) which invokes $i(false)
     * before returning here for (possible) initialization.
     */
    return new c(c).$i(args); // Subclasses
};