Javascript inheritance

1. Introduction

The UIZE JavaScript Framework provides a robust - yet compact - system for class inheritance. This document discusses various aspects of the class inheritance mechanism.

2. It's Easy

Subclassing is very easy in the UIZE JavaScript Framework. Creating a fresh subclass is as simple as the following statement...

var MySubclass = MySuperclass.subclass ();

3. Where It's Implemented

The power of classes built using the UIZE JavaScript Framework is derived from the Uize base class.

All classes inherit directly - or indirectly - from this class. While the Uize base class module is not tiny, the core of the inheritance mechanism itself is quite tiny and is encapsulated in a non-public class called Root that is, technically, the superclass for the Uize class. What makes the Uize module larger is that it provides not only an inheritance mechanism, but also a JavaScript Event System, a Set-get Properties system, a JavaScript Modules system, and scores of utility methods.

4. Fleshing Out a Subclass

Each class that you create using the framework inherits the subclass static method, so you can easily make further subclasses of them.

EXAMPLE

var MyClass = Uize.subclass ();       // create a class
MyClass.prototype.foo = 'bar';        // create an instance property for the class

var MySubclass = MyClass.subclass (); // subclass the class
mySubclassInstance = new MySubclass;  // create an instance of the subclass

alert (mySubclassInstance.foo);       // alert the subclass instance's foo property

In this example, MyClass is a plain vanilla subclass of the Uize base class. The property foo is assigned on its prototype and is shared by all instances. Now, a subclass of MyClass is created, by the name of MySubclass. An instance of MySubclass is created, and the value of its foo property is alerted. Unsurprisingly, what shows up in the alert dialog is the text "bar". Naturally, meaningful classes are a little more complex than this, but this demonstrates the basics.

The Uize base class provides a mechanism for inheriting instance methods and properties assigned on a superclass' prototype, static methods and properties assigned on the superclass, and set-get properties registered on the superclass using the registerProperties static method. The inheritance system that is implemented in the Uize base class overcomes some of the weaknesses of a superficial prototype-based approach to inheritance, but we'll get into that a little bit later.

Once you have a subclass, you can modify it by assigning values on its prototype object, adding static methods or properties to the subclass, and registering set-get properties or overriding the initial values of inherited set-get properties.

4.1. Adding Instance Methods and Properties

Adding instance methods and properties to a subclass involves assigning properties on the subclass' prototype object.

EXAMPLE

var MyClass = Uize.subclass ();                     // create a class
MyClass.prototype.myInstanceProperty = 'value';     // create an instance property
MyClass.prototype.myInstanceMethod = function () {  // create an instance method
  // do stuff
};

In the UIZE JavaScript Framework, instance methods and properties added to the prototype object of a class are copied to the prototype object of a subclass when creating that subclass. After a subclass is created, modifying the prototype object of its superclass has no effect on that subclass.

4.2. Adding Static Methods and Properties

Adding instance methods and properties to a subclass involves assigning properties on the subclass' prototype object.

EXAMPLE

var MyClass = Uize.subclass ();         // create a class
MyClass.myStaticProperty = 'value';     // create a static property
MyClass.myStaticMethod = function () {  // create a static method
  // do stuff
};

IMPORTANT

In the UIZE JavaScript Framework, static methods and properties assigned on a class are copied to a subclass when creating that subclass. After a subclass is created, modifying the static methods and properties of the superclass has no effect on that subclass.

4.3. Adding Set-get Properties

You can register one or more set-get properties for a subclass, using the Uize.registerProperties static method.

EXAMPLE

var MyClass = Uize.subclass ();  // create a class
MyClass.registerProperties ({    // register one or more set-get properties
  _mySetGetProperty:{
    name:'mySetGetProperty',
    onChange:function () {
      // code to handle a change in the property's value
    },
    value:'initialValue'
  }
});

4.3.1. Inheriting Set-get Properties

Set-get properties registered for a class are inherited when creating a subclass.

EXAMPLE

var MyClass = Uize.subclass ();       // create a class
MyClass.registerProperties ({         // register the "foo" set-get property
  _foo:{
    name:'foo',
    onChange:function () {
      // code to handle a change in foo's value
    },
    value:'initial value for foo'
  }
});

var MySubclass = MyClass.subclass (); // create a subclass
MySubclass.registerProperties ({      // register the "bar" set-get property
  _bar:{
    name:'bar',
    onChange:function () {
      // code to handle a change in bar's value
    },
    value:'initial value for bar'
  }
});

After the above code has executed, the class MySubclass will have the two set-get properties foo and bar.

5. Overriding a Superclass

5.1. Overriding Instance Methods and Properties

Overriding instance methods and properties of a superclass is easy. It's just a matter of re-assigning.

EXAMPLE

var MyClass = Uize.subclass ();            // create a class
MyClass.prototype.foo = function () {      // create an instance method
  alert ('bar');
};

var MySubclass = MyClass.subclass ();      // subclass the class
MySubclass.prototype.foo = function () {   // override inherited instance method
  alert ('foo');
  MyClass.prototype.foo.call (this);      // call foo method on superclass
};

myClassInstance = new MyClass;             // create an instance of the class
myClassInstance.foo ();                    // call foo method on instance of class

mySubclassInstance = new MySubclass;       // create an instance of the subclass
mySubclassInstance.foo ();                 // call foo method on instance of subclass

In this example, the foo instance method of the subclass has been overrided so that it first alerts the text "foo" and then calls the foo method implementation from the superclass and alerts the text "bar".

Typically, the code that implements the methods of a subclass is near (or in the same scope as) the code that creates the subclass, so the subclass knows its superclass. When you start to digging into subclass modules, you will encounter this reference to the superclass in the form of the _superclass variable (named such by convention).

5.2. Overriding Static Methods and Properties

Overriding static methods and properties of a superclass is easy. It's just a matter of re-assigning.

EXAMPLE

var MyClass = Uize.subclass ();            // create a class
MyClass.myStaticMethod = function () {     // create a static method
  alert ('foo');
};

var MySubclass = MyClass.subclass ();      // subclass the class
MySubclass.myStaticMethod = function () {  // override inherited static method
  alert ('bar');
};

myClass.myStaticMethod ();                 // call myStaticMethod on MyClass
mySubclass.myStaticMethod ();              // call myStaticMethod on MySubclass

In this example, MySubclass is a subclass of MyClass, and both classes have a static method myStaticMethod. By subclassing MyClass, MySubclass inherits the implementation of myStaticMethod from MyClass. Re-assigning the myStaticMethod property of MySubclass overrides the inherited implementation without affecting MyClass. So, calling myStaticMethod on MyClass will product the text "foo" in an alert dialog, while calling myStaticMethod on MySubclass will product the text "bar" in an alert dialog.

5.2.1. Calling a Subclass Version of a Static Method

One may implement a class in such a way that a static method is intended to be overrided by a subclass. And in such cases, one may also wish the superclass' implementation to be guaranteed to always use the subclass' version of the static method.

Calling a subclass' version of a static method can be done in two ways, depending on whether the code is in the implementation for an instance method or for a static method of the superclass.

5.2.1.1. The Instance Method Case

Within an instance method's implementation, one can use the Class instance property to reference the actual class of the instance - even in the superclass' code - as in...

MyClass.prototype.myInstanceMethod = function () {
  this.Class.myStaticMethod (); // call myStaticMethod static method on subclass
};

Now, in the case of an instance of MyClass, the myStaticMethod static method will be called on MyClass. However, in the case of an instance of MySubclass (that is a subclass of MyClass), the myStaticMethod static method will be called on MySubclass, even though the myInstanceMethod instance method is implemented by MyClass. If MySubclass didn't override the implementation inherited from MyClass, then there will be no difference in the outcome.

Let's take a look at an example...

EXAMPLE

MyClass = Uize.subclass ();                // create a class
MyClass.myStaticMethod = function () {     // create a static method
  alert ('MyClass');
};
MyClass.myInstanceMethod = function () {   // create an instance method
  this.Class.myStaticMethod ();
};

MySubclass = MyClass.subclass ();          // subclass the class
MySubclass.myStaticMethod = function () {  // override inherited static method
  alert ('MySubclass');
};

var _mySubclassInstance = new MySubclass;  // create instance of MySubclass
mySubclassInstance.myInstanceMethod ();    // call myInstanceMethod

In this example, MyClass implements the static method MyClass.myStaticMethod. Now, MySubclass overrides the implementation of myStaticMethod inherited from MyClass. The implementation of myInstanceMethod (inherited from MyClass) uses the Class property to get a reference to the actual class of an instance on which the method is being called. So, when this method is called on the instance of MySubclass named mySubclassInstance, the overrided form of myStaticMethod (implemented by MySubclass) is called, resulting in the text "MySubclass" being displayed in the alert dialog - not the text "MyClass".

5.2.1.2. The Static Method Case

Within a static method's implementation, one can use the this keyword to reference the actual class - even in the superclass' code - as in...

MyClass.myStaticMethod = function () {
  this.myOtherStaticMethod ();
};

Let's take a look at an example...

EXAMPLE

MyClass = Uize.subclass ();                     // create a class
MyClass.myStaticMethod = function () {          // create a static method
  this.myOtherStaticMethod ();
};
MyClass.myOtherStaticMethod = function () {     // create another static method
  alert ('MyClass');
};

MySubclass = MyClass.subclass ();               // subclass the class
MySubclass.myOtherStaticMethod = function () {  // override inherited static method
  alert ('MySubclass');
};

MySubclass.myStaticMethod ();                   // call inherited static method

In this example, MyClass implements the two static methods MyClass.myStaticMethod and MyClass.myOtherStaticMethod. The implementation of MyClass.myStaticMethod wants to always call the subclass' version of myOtherStaticMethod. So, instead of calling it as MyClass.myOtherStaticMethod (), it calls it as this.myOtherStaticMethod (). If myStaticMethod is called on MyClass, then this will be a reference to MyClass. However, if myStaticMethod is called on MySubclass, then this will be a reference to MySubclass. Consequently, using this guarantees that the subclass' version will be called. In this example, MySubclass overrides the implementation of myOtherStaticMethod inherited from MyClass, so the statement MySubclass.myStaticMethod () will result in the text "MySubclass" being displayed in the alert dialog - not the text "MyClass".

5.3. Overriding Set-get Property Values

The initial value for a set-get property inherited from a superclass can be overrided by calling the Uize.set static method on the subclass.

The initial value for a set-get property is registered by specifying the value property in the profile for the set-get property, as follows...

EXAMPLE

var MyClass = Uize.subclass ();        // create a class
MyClass.registerProperties ({          // register the foo set-get property
  _foo:{
    name:'foo',
    onChange:function () {
      // code to handle a change in foo's value
    },
    value:'bar'
  }
});

var _myClassInstance = new MyClass;    // create instance of MyClass
alert (_myClassInstance.get ('foo'));  // alert value of foo set-get property

In the above example, the text "bar" will be displayed in the alert dialog.

Now, the initial value for the foo set-get property inherited from MyClass can be overrided in a subclass, as follows...

EXAMPLE

var MyClass = Uize.subclass ();            // create a class
MyClass.registerProperties ({              // register the foo set-get property
  _foo:{
    name:'foo',
    onChange:function () {
      // code to handle a change in foo's value
    },
    value:'bar'
  }
});

var MySubclass = MyClass.subclass ();      // subclass the class
MySubclass.set ({foo:'NOT BAR'});          // override initial value for foo

var _mySubclassInstance = new MySubclass;  // create instance of MySubclass
alert (_mySubclassInstance.get ('foo'));   // alert value of foo set-get property

In this example, we have now created a subclass of MyClass named MySubclass. The new subclass inherits the foo set-get property, along with its initial value of 'bar' that is registered in MyClass. The MySubclass.set static method is then used to change the initial value of this set-get property to 'NOT BAR'. Now, the fresh instance of MySubclass that is created, named _mySubclassInstance, will have the initial value 'NOT BAR' for its foo set-get property, and the alert dialog will display this text.

6. Private vs. Public

By convention, private methods and properties - both instance and static - are distinguished from public methods and properties by prefixing their names with an underscore.

EXAMPLE

_classPrototype.myPublicInstanceMethod = function () {    // public instance method
  // do stuff
};
_class.myPublicStaticMethod = function () {               // public static method
  // do stuff
};

_classPrototype._myPrivateInstanceMethod = function () {  // private instance method
  // do stuff
};
_class._myPrivateStaticMethod = function () {             // private static method
  // do stuff
};

Technically, such private methods and properties are not thoroughly private and can be accessed as public methods or properties. However, because their names will be changed by the Scruncher when the code is scrunched to minimize its size, one cannot reliably access these methods and properties. This is a kind of "soft privacy".

IMPORTANT

When creating subclasses, it is important not to access methods or properties defined in the superclass with private names. The code will work when it is unscrunched, but there is no guarantee what your subclass will be accessing when all the code is scrunched.

6.1. Private and Public References

Sometimes, for size optimization, it can be helpful to assign a private reference to a public instance method.

EXAMPLE

_classPrototype.myInstanceMethod = _classPrototype._myInstanceMethod = function () {
  // do stuff
};

In this example, the private instance method _myInstanceMethod is mapped to the public instance method myInstanceMethod. Inside the class' implementation, the private name can be used. This is useful if there will be many calls - in the class' implementation - to an instance method that's publicly accessible, as the private name will be reduced in size by the Scruncher.

IMPORTANT

Be careful when using this technique, because using a private reference to a public instance method doesn't ensure that a class' implementation will be calling a subclass' override of the instance method, since the subclass won't assign a value for the private identifier.
This technique is not useful for public instance properties that are expected to be assigned through the public interface, since assigning a value using the public name will not assign that same value to the private version.
This technique could be used for public instance properties that are intended to be read only and are not intended to be set outside of the class' implementation code. The technique could also be used if the value of the property was an object and was never intended to be changed, even if the contents of the object is expected to be modified by code outside of the class' implementation.

7. Phases of Construction

When an instance of a UIZE class is created, construction involves three phases: the alphastructor phase, the set-get property initialization phase, and the omegastructor phase.

7.1. alphastructor

The alphastructor is a function that is executed before the set-get properties for an instance are initialized, and before the omegastructor function is executed.

An alphastructor is registered by specifying a value for the first parameter when calling the subclass static method on the superclass.

EXAMPLE

var MyClass = Uize.subclass (
  function () {
    this._someArray = [];
  }
);

Code inside the alphastructor can set up private "scaffolding" type properties that need to be already in existence by the time any of the set-get properties are initialized. This allows onChange handlers for the set-get properties to count on certain foundation having been built.

Because of when it is executed, code inside the alphastructor function cannot rely on the instance's set-get properties being initialized to the values passed to the constructor.

EXAMPLE

var
  MyClass = Uize.subclass (
    function () {
      alert (this.get ('foo'));
    }
  ),
  myClass = new MyClass ({foo:'bar'})
;

In this example, when the new instance of MyClass is created with the value 'bar' specified for its foo set-get property, the alert inside the alphastructor will display the value undefined. This is by design and is an important nuance to take note of.

7.2. omegastructor

The omegastructor is a function that is executed after the alphastructor function is executed, and after the set-get properties for an instance are initialized.

8. Dissecting a Subclass Module

8.1. An Example

Let's take a look at a skeleton of a subclass module, and then explore various aspects of it.

SUBCLASS MODULE EXAMPLE

Uize.module ({
  name:'Uize.Subclass',
  builder:function (_superclass) {
    /*** Class Constructor ***/
      var
        _class = _superclass.subclass (
          null, // typically don't need to do anything in the alphastructor
          function () {
            var _this = this;

            /*** Private Instance Properties ***/
              _this._privateInstanceProperty = 0;
          }
        ),
        _classPrototype = _class.prototype
      ;

    /*** Private Instance Methods ***/
      _classPrototype._privateInstanceMethod = function () {
        var _this = this;
        // implementation for this method
      };

    /*** Public Instance Methods ***/
      _classPrototype.publicInstanceMethod = function () {
        var _this = this;
        // implementation for this method
      };

    /*** Private Static-instance Methods ***/
      _class._privateStaticInstanceMethod =
      _classPrototype._privateStaticInstanceMethod = function () {
        // implementation for this method
      };

    /*** Public Static-instance Methods ***/
      _class.publicStaticInstanceMethod =
      _classPrototype.publicStaticInstanceMethod = function () {
        // implementation for this method
      };

    /*** Public Static Methods ***/
      _class.publicStaticMethod = function () {
        // implementation for this method
      };

    /*** Register Properties ***/
      _class.registerProperties ({
        _property1:'property1', // minimalistic approach to registering a property
        _property2:{
          name:'property2',
          onChange:function () {
            // do something when the value of this property changes
          },
          value:0
        }
      });

    return _class;
  }
});

NOTE: If you're not familiar with module declarations in the UIZE JavaScript Framework, consult the explainer JavaScript Modules.

8.2. The Superclass Reference

A reference to the superclass is provided as the parameter to the builder function. By convention, this is named _superclass.

This variable can then be used when creating the subclass, as in...

var _class = _superclass.subclass ();

The superclass reference can also be used when calling a superclass' version of an instance method, as in...

_classPrototype.wireUi = function () {
  var _this = this;
  if (!_this.wired ()) {
    // do the wiring stuff

    _superclass.prototype.wireUi.call (_this); // call superclass version of wireUi
  }
};

8.3. Keeping a Class Reference

A reference to the newly created subclass is kept in a variable. By convention, this variable is named _class.

var _class = _superclass.subclass ();

This variable can then be used when assigning static methods and properties, as in...

_class.myStaticMethod = function () {
  // do stuff
};

...or when registering set-get properties, as in...

_class.registerProperties ({
  // profiles for the set-get properties
});

The reference to the newly created class is returned at the end of the builder function, as in...

return _class;

8.4. Keeping a Prototype Reference

A reference to the class' prototype property is stored in a _classPrototype variable.

var _classPrototype = _class.prototype;

This variable can then be used when assigning instance methods and properties that are to be shared by all instances, as in...

_classPrototype.myInstanceMethod = function () {
  // do stuff
};

8.5. Assigning an Instance Method

Using the _classPrototype reference, an instance method can be assigned for the class.

_classPrototype.myInstanceMethod = function () {
  // do stuff
};

IMPORTANT

It is easy to fall into the trap of ommitting the semi-colon when assigning an anonymous function to a property of the prototype object. Even though there is a function declaration, it is used as part of an assignment statement, and an assignment statement should end with a semi-colon. Leaving the semi-colon out will produce no symptoms until you scrunch the code and then another statement butts right up against your assignment, producing a syntax error. So, don't forget the semi-colon.

8.6. Assigning a Static Method

Using the _class reference, a static method can be assigned for the class.

_class.myStaticMethod = function () {
  // do stuff
};

8.7. Registering Set-get Properties

Set-get properties are registered for a class by calling the Uize.registerProperties static method that is inherited from the Uize base class.

The Uize.registerProperties static method lets you register one or more set-get properties in a single call to the method. The single parameter that the Uize.registerProperties static method takes is an object containing profiles for all the set-get properties being registered, where each key is the private name for a set-get property, and where each value is either the public name - or an object providing a complete profile - for the set-get property.

EXAMPLE

_class.registerProperties ({
  _property1:'property1', // minimalistic approach to registering a property
  _property2:{
    name:'property2',
    onChange:function () {
      // do something when the value of this property changes
    },
    value:0
  }
});

In the above example, the set-get property with the public name property1 is being mapped to the private identifier _property1. It is not technically necessary that the private name be the public name prefixed with an underscore - this is simply a convention that is followed. Because no object is used to specify the profile for this set-get property, there is no onChange handler registered, and the initial value is undefined. By contrast, the property2 set-get property is registered with a profile object that specifies an onChange handler function and an initial value of 0.