Javascript Inheritance - Ajax Patterns

Javascript Inheritance

From Ajax Patterns

Contents

Ajax/Javascript Programming and Usability in "Ajax Design Patterns" Book

Background

As web applications become more dynamic and responsive to the user, they tend to use an increasing amount of client-side JavaScript. As a result, many web developers have sought ways to apply object oriented techniques to their JavaScript in order to manage its increasing complexity. Since JavaScript is not a classical object oriented language, a number of different approaches have been crafted over the years to pull off various levels of object oriented behavior. Below you find a listing of different patterns that have been seen in use throughout the web development community.

Each pattern has been given a unique name so that developers will have common terminology by which to refer to them. Additionally, a working code example of each pattern is provided along with any benefits or negatives. Some of the patterns are more complex than others and therefore require supporting code. In those cases, the supporting code will be included in a separate code section above the example so that the example can remain focused on the usage of the pattern.

Prototype Inheritance

This is probably the most common form of JavaScript class definition and inheritance. It's not really much of a pattern because this is essentially the approach that was bullt into the language by its creators. The 'prototype' member exists on all JavaScript objects and since all functions in the language are object instances, you can access the prototype member of any function that has been defined.

Benefits

  • Ahead of time class definition
  • Natively supported by language
  • Fast object instance creation
  • Functions and properties defined once per prototype, not once per object
  • Lesser memory footprint

Negatives

  • Fragmented syntax for class definition
  • Verbose due to duplicate class name references
  • Visually appears more procedural/functional than object oriented

Example

function BaseClass() {
    //BaseClass constructor code goes here 
}

BaseClass.prototype.getName = function() {
    return "BaseClass";
}

function SubClass() {
    //SubClass constructor code goes here 
}

//Inherit the methods of BaseClass
SubClass.prototype = new BaseClass();

//Override the parent's getName method
SubClass.prototype.getName = function() {
    return "SubClass";
}

//Alerts "SubClass"
alert(new SubClass().getName());

Closure Inheritance

Unlike the approach taken by Prototype Inheritance, this approach leverages a feature of JavaScript known as closures in order to assemble the methods and properites of an object during its construction. A closure is essentially the ability to define an inner function within another function that can access the variables and functions defined within the scope it was created. While closures are a powerful language construct, there are numerous performance and memory considerations that must be taken into account when developing Ajax applications. In particular, the automatic garbage collector in the IE browser has a major flaw that prevents it from collecting DOM objects that are referenced from within a closure. Therefore, it is generally recommended that you avoid this approach in your client-side Ajax programming.

Benefits

  • Fairly clean syntax for class definition
  • Natively supported by language
  • Concise, eliminates duplication in definition
  • Supports differences in class definition between instances
  • Only way to establish methods and properties that are truely private.

Negatives

  • Run time class definition is slower
  • Slow object instance creation
  • May result in SEVERE browser memory leaks caused by closures interacting with DOM objects. Mainly an issue with IE although some issues do exist in other browsers.

Example

function BaseClass() {
    this.getName = function() {
        return "BaseClass";
    };    

    //BaseClass constructor code goes here 
}

function SubClass() {
    //Override the parent's getName method
    this.getName = function() {
        return "SubClass";
    }

    //SubClass constructor code goes here 
}

//Inherit the methods of BaseClass
SubClass.prototype = new BaseClass();

//Alerts "SubClass"
alert(new SubClass().getName());

Direct Inheritance (ThinWire Ajax Framework)

Supporting Code

//Defines the top level Class
function Class() { }
Class.prototype.construct = function() {};
Class.extend = function(def) {
    var classDef = function() {
        if (arguments[0] !== Class) { this.construct.apply(this, arguments); }
    };
    
    var proto = new this(Class);
    var superClass = this.prototype;
    
    for (var n in def) {
        var item = def[n];                        
        if (item instanceof Function) item.$ = superClass;
        proto[n] = item;
    }

    classDef.prototype = proto;
    
    //Give this new class the same static extend method    
    classDef.extend = this.extend;        
    return classDef;
};

Example

var BaseClass = Class.extend({
    construct: function() {
        //BaseClass constructor code goes here
    },
        
    getName: function() {
        return "BaseClass(" + this.getId() + ")";
    },
    
    getId: function() {
        return 1;
    }
});

var SubClass = BaseClass.extend({
    construct: function() {
        //SubClass constructor code goes here
    },

    //Override BaseClass's getName method
    getName: function() {
        return "SubClass(" + this.getId() + ") extends " +
            arguments.callee.$.getName.call(this);
    },
    
    //Override BaseClass's getId method
    getId: function() {
        return 2;
    }
});

var TopClass = SubClass.extend({
    //Override SubClass's getName method
    getName: function() {
        //Calls the getName() method of SubClass
        return "TopClass(" + this.getId() + ") extends " +
            arguments.callee.$.getName.call(this);
    },
    
    //Override SubClass's getId method
    getId: function() {
        //Just like the last example, this.getId()
        //always returns the proper value of 2.        
        return arguments.callee.$.getId.call(this);
    }
});

//Alerts "TopClass(2) extends SubClass(2) extends BaseClass(2)"
alert(new TopClass().getName());

Bound Inheritance (ThinWire Ajax Framework)

Supporting Code

function Class() { }
Class.prototype.construct = function() {};
Class.__asMethod__ = function(func, superClass) {    
    return function() {
        var currentSuperClass = this.$;
        this.$ = superClass;
        var ret = func.apply(this, arguments);        
        this.$ = currentSuperClass;
        return ret;
    };
};

Class.extend = function(def) {
    var classDef = function() {
        if (arguments[0] !== Class) { this.construct.apply(this, arguments); }
    };
    
    var proto = new this(Class);
    var superClass = this.prototype;
    
    for (var n in def) {
        var item = def[n];                        
        
        if (item instanceof Function) {
            item = Class.__asMethod__(item, superClass);
        }
        
        proto[n] = item;
    }

    proto.$ = superClass;
    classDef.prototype = proto;    
    classDef.extend = this.extend;        
    return classDef;
};

Example

//Hey, this class definition approach
//looks much cleaner than then others.
var BaseClass = Class.extend({
    construct: function() {
        //BaseClass constructor code goes here
    },
    
    getName: function() {
        return "BaseClass(" + this.getId() + ")";
    },
    
    getId: function() {
        return 1;
    }
});

var SubClass = BaseClass.extend({
    construct: function() {
        //SubClass constructor code goes here
    },

    //Override BaseClass's getName method
    getName: function() {
        return "SubClass(" + this.getId() + ") extends " +
            this.$.getName.call(this);
    },
    
    //Override BaseClass's getId method
    getId: function() {
        return 2;
    }
});

var TopClass = SubClass.extend({
    construct: function() {
        //TopClass constructor code goes here
    },

    //Override SubClass's getName method
    getName: function() {
        return "TopClass(" + this.getId() + ") extends " +
            this.$.getName.call(this);
    },
    
    //Override SubClass's getId method
    getId: function() {
        return this.$.getId.call(this);
    }
});

//Alerts "TopClass(2) extends SubClass(2) extends BaseClass(2)"
alert(new TopClass().getName());

Reference Inheritance (Douglas Crockford) [BROKEN]

Based on the standard Prototype Inheritance approach and named for it's super class reference walking, this general approach became widely known after Douglas Crockford published his paper entitled link Classical Inheritance in JavaScript. Since then a number of similar approaches have appeared as an increasing number of developers have a sought a way to handle super-class method calling in JavaScript. Approaches like this are popular and widely spread because on the surface they appear to work correctly. However, these approaches tend to break down when the method call stack goes deeper than one super-class call. The following example demonstrates this failure:

Supporting Code

Function.prototype.inherits = function(parent) {
    var d = 0, p = (this.prototype = new parent());
    
    this.prototype.uber = function(name) {
        var f, r, t = d, v = parent.prototype;
        if (t) {
            while (t) {
                v = v.constructor.prototype;
                t -= 1;
            }
            f = v[name];
        } else {
            f = p[name];
            if (f == this[name]) {
                f = v[name];
            }
        }
        d += 1;
        r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
        d -= 1;
        return r;
    };
};

Example

function BaseClass() {
    //BaseClass constructor code goes here 
}

BaseClass.prototype.getName = function() {
    return "BaseClass(" + this.getId() + ")";
}

BaseClass.prototype.getId = function() {
    return 1;
}


function SubClass() {
    //SubClass constructor code goes here 
}

//Inherit the methods of BaseClass
//and the 'uber' super calling method
SubClass.inherits(BaseClass);

//Override the parent's getName method
SubClass.prototype.getName = function() {
    return "SubClass(" + this.getId() + ") extends " +
        this.uber("getName");
}

//Override the parent's getId method
SubClass.prototype.getId = function() {
    return 2;
}


function TopClass() {
    //TopClass constructor code goes here 
}

//Inherit the methods of SubClass
//and the 'uber' super calling method
TopClass.inherits(SubClass);

TopClass.prototype.getName = function() {
    return "TopClass(" + this.getId() + ") extends " +
        this.uber("getName");
}

TopClass.prototype.getId = function() {
    return this.uber("getId");
}

//INCORRECT: Alerts "TopClass(2) extends SubClass(1) extends BaseClass(1)"
alert(new TopClass().getName());

But there is a patched version (Note: for users which can't access blogspot (from Mainland China, India, Pakistan and Iran), please visit inblogs mirror)

Patched Code

    Function.prototype.inherits = function(parent) {
        var d = {}, p = (this.prototype = new parent());
        
        this.prototype.uber = function(name) {
         if (!(name in d)) d[name] = 0;
            var f, r, t = d[name], v = parent.prototype;
            if (t) {
                while (t) {
                    v = v.constructor.prototype;
                    t -= 1;
                }
                f = v[name];
            } else {
                f = p[name];
                if (f == this[name]) {
                    f = v[name];
                }
            }
            d[name] += 1;
            r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
            d[name] -= 1;
            return r;
        }
    }

The problem is because of d, which indicates the depth of supercall. In original code, every method share the same d, which will cause bug when nest call (one method call another). So give every method their own counter, solves the problem.

Lazy Inheritance (JSINER)

"Lazy inheritance" is a design pattern used in JavaScript computer programming. It designates a postponed linking of an object with it's prototype (class) until it is needed. If used properly, such approach may increase efficiency, simplicity and flexibility of OOP based code written using JavaScript. Unlike of "classical" approach, it supports a "Lazy" way of inheritance implementation, since reference to parent class is invoked only at the moment of object instance creation. Basically, it's possible to say that instances of objects in such approach are created in "mixed" mode – on first invocation, an appropriate factory is used to modify object constructor prototype which is later used for subsequent object instances creation. Moreover, since "Lazy inheritance" is called only once at the moment of first object instance creation, it becomes possible to combine process of class prototype creation with resolving necessary dependencies of that class. In other words, the process of prototype construction also allows loading scripts (if ones were not loaded before) which particular class depends on.

Benefits

Such approach to inheritance support has the following benefits:

  • Lazy inheritance has the same benefits as classical JavaScript prototype features;
  • It is not necessary to maintain proper order of script files declaration;
  • In most cases, HTML page is loaded faster since there are no objects created during page initialization and since some scripts could be loaded only at the moment in which they are actually necessary (they are necessary if the caller code during it's execution really creates instances of corresponding classes);
  • Dependencies are declared in more explicit way and class depends only on concrete scripts; Moreover, it's not necessary to artificially group particular scripts into "modules" and define dependencies between such "modules";
  • If lazy mode of scripts loading is used, only necessary scripts (which are actually required for application functionality) will be loaded. If, for example, some HTML page may operate in different modes, like "read-write" and "read-only", for every mode required scripts will be loaded and this will be performed automatically without manual script loading optimization.

Negatives

  • uses synchronous xmlhttp which really, really is a bad thing to the browser UI (if a lot of scripts need to be loaded and or size of javascript files)

Lazy Inheritance Internals

Lazy inheritance is invoked within the Object' constructor code and is called only once for first object instance creation. After invocation, the following steps:

  • First, the algorithm tests Object' dependencies;
  • If Object' dependency had been registered before the object constructor was called (interrelations can be defined in the object constructor too), corresponding JavaScript dependencies are loaded.
  • Then, if the class, from which the object inherits, is defined and it is not a default one (Object), Object's constructor prototype is modified by classical schema of prototype-based inheritance.
  • After completing prototype modification, constructor of the Object' is called again to apply changes to new instance of the Object.

The derived instance of the Object may be modified in the constructor according to particular logic. For example, some properties of the object may be defined. The resulting object is the same JavaScript object as it would be created with prototype-based inheritance.

Example

First, we define Person. Let's assume that declaration of that class is placed to person.js JavaScript file.

  /**
   * Constructor for Person class
   */ 
  function Person(aName)
  {
    this.fName = aName;
  }
  /**
   * Function which returns name of Person’s.
   */
  Person.prototype.getName = function()
  {
    return this.fName;
  };
  /**
   * Here we define function which returns string representation of Person
   * assign it to prototype of Person class
   */ 
  Person.prototype.toString = function()
  {
    return "Person: " + this.getName();
  };

Here we assume that the code below is placed to employee.js file.

 /* 
  * Here we define that Employee class depends on person script. 
  */ 
 JSINER.addDependency( {Employee:"person"} );
 /**
  * Here we define constructor for Employee class 
  * and declare that Employee class inherits Person one
  */ 
 function Employee(aName, aUID)
 {
   var self = JSINER.extend(this, "Person");
   self.fName = aName;
   self.fUID = aUID;
   return self;
 }
 /** 
  * Here we define function which return UID of employee 
  * and assign it to prototype of Employee class.
  */    
 Employee.prototype.getUID = function()
 {
   return this.fUID;
 };
 /**
  * Here we define function which returns string representation of Employee
  */
 Employee.prototype.toString = function()
 {
    var person = Employee.superClass.toString.call(this);
    return this.getUID() + ":" + person;
 };

And this is sample HTML page which uses declarations of JavaScript object above:

 <html>
   <head>
     <title>Lazy inheritance example.</title>
      // We need to include reference to JSNIER code
     <script src="script/jsiner.js"></script>
     // Note that we include reference to script that contains Employee class only
     <script src="script/employee.js"></script>
     // And no it's not required to reference person.js script explicitely -
     // JSINER will resolve and load it automatically
  </head>
  <body>
    <script>
       var employee = new Employee('John Doe', 1212);
       alert(employee);
     </script>
  </body>
</html>

During execution of example code listed above, linkage of Employee and Person as well as loading of necessary script "person.js" is performed automatically with the first creation of Employee instance.

As it is illustrated by examples above, "Lazy inheritance":

  • It is called only once during first creation of object instance;
  • Linkage of objects and scripts is performed automatically;
  • To link classes the prototype based inheritance is used.

In general, the proposed scheme considers "lazy" loading as primary mode for loading particular scripts. However, this is not strict requirement – it is also possible to force loading of all scripts which form particular application before starting objects creation if this way of loading is required by particular application architecture.