EXPLAINERS Javascript Inheritance

1. Introduction

The UIZE JavaScript Framework implements a system for class inheritance that makes subclassing and object-oriented programming possible in JavaScript.

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.Class base class.

All classes inherit directly - or indirectly - from this class. While the Uize.Class module is not tiny, the core of the inheritance mechanism itself is quite tiny. What makes the Uize.Class module larger is that it provides not only an inheritance mechanism, but also a JavaScript Event System and a State Properties system.

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.Class.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.Class 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.Class base class provides a mechanism for inheriting instance methods and properties assigned on a superclass' prototype, static methods and properties assigned on the superclass (unless they are registered as non-inheritable statics), and state properties registered on the superclass using the registerProperties static method. The inheritance system that is implemented in the Uize.Class 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 state properties or overriding the initial values of inherited state 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.Class.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.Class.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 (unless they are registered as non-inheritable statics). After a subclass is created, modifying the static methods and properties of the superclass has no effect on that subclass.

4.3. Adding State Properties

You can register one or more state properties for a subclass, using the MyClass.registerProperties static method that is inherited from the Uize.Class base class.

EXAMPLE

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

4.3.1. Inheriting State Properties

State properties registered for a class are inherited when creating a subclass.

EXAMPLE

var MyClass = Uize.Class.subclass ();  // create a class
MyClass.registerProperties ({          // register the "foo" state 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" state 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 state 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.Class.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.Class.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.Class.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.Class.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 State Property Values

The initial value for a state property inherited from a superclass can be overrided by calling the MyClass.set static method that is inherited from the Uize.Class base class.

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

EXAMPLE

var MyClass = Uize.Class.subclass ();  // create a class
MyClass.registerProperties ({          // register the foo state 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 state property

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

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

EXAMPLE

var MyClass = Uize.Class.subclass ();      // create a class
MyClass.registerProperties ({              // register the foo state 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 state property

In this example, we have now created a subclass of MyClass named MySubclass. The new subclass inherits the foo state 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 state 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 state 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. Non-inheritable Statics

Unless otherwise specified, all static features - methods as well as properties - of a Uize.Class subclass are inherited in turn by their subclasses.

There are times, however, when there is no compelling reason for a static feature of a class to be inherited by its subclasses. In fact, in some cases too many inherited static features can just clutter subclasses in the class hierarchy with cruft that they neither need nor care about. In such cases, the inheritance system of the UIZE JavaScript Framework provides a way to "register" static features of a class as being non-inheritable. This is done using the MyClass.nonInheritableStatics static property.

7.1. The MyClass.nonInheritableStatics Static Property

The MyClass.nonInheritableStatics static property is a lookup object, automatically created for a class, in which you can register the static features (methods or properties) of the class that should not be inherited when that class is subclassed.

Each property of the MyClass.nonInheritableStatics lookup object represents a single static feature of the class that should not be inherited by subclasses, where the name of each property should be the name of a static feature (excluding the module name), and the value of each property should be a truthy value (such as true, 1, 'foo', [], {}, etc.). After a class has been created, non-inheritable statics can be registered for that class by assigning properties to the class' MyClass.nonInheritableStatics static property, as shown in the example below...

EXAMPLE

MyClass = Uize.Class.subclass ();
MyClass.someUtilityFunction = function () {
  // do something of great utility
};
MyClass.nonInheritableStatics.someUtilityFunction = 1;

MyClassSubclass = MyClass.subclass ();
alert (MyClassSubclass.someUtilityFunction); // alerts the text "undefined"

In the above example, the MyClass.someUtilityFunction static method of the class MyClass has been registered as a non-inheritable static. This is done by the statement MyClass.nonInheritableStatics.someUtilityFunction = 1. Now, when the MyClassSubclass class is created by calling the MyClass.subclass method, the new subclass that is created does not get the someUtilityFunction static feature. Therefore, the alert statement displays the text "undefined" in the alert dialog.

7.1.1. nonInheritableStatics is a Non-inheritable Static

When a class is created, the MyClass.nonInheritableStatics static property is automatically initialized on that class to a fresh object with the value {nonInheritableStatics:1}.

This initial mapping means that the MyClass.nonInheritableStatics static property is, itself, not inheritable by subclasses - subclasses get their own fresh object. So, in our example, when the MyClassSubclass subclass is created, its fresh MyClassSubclass.nonInheritableStatics property does not have an entry for the someUtilityFunction static feature, because it does not have that static feature and the contents of the MyClass.someUtilityFunction object is not inherited by the MyClassSubclass class.

7.2. All Types of Statics Can Be Non-inheritable

All types of static features of a class can be registered as non-inheritable - methods as well as properties.

Registering static properties as non-inheritable is accomplished in the same way as for static methods. Consider the following example...

EXAMPLE

MyClass = Uize.Class.subclass ();
MyClass.someStaticProperty = 'foo';
MyClass.nonInheritableStatics.someStaticProperty = 1;

MyClassSubclass = MyClass.subclass ();
alert (MyClassSubclass.someStaticProperty); // alerts the text "undefined"

In the above example, the alert dialog would display the text "undefined", because the MyClass.someStaticProperty static property of the class MyClass has been registered as non-inheritable, so it is not inherited by the MyClassSubclass class that is created by subclassing MyClass.

7.3. Utility Functions Need Not Be Inheritable

A good case for making a static method non-inheritable is if it is really just a utility function that happens to be stashed in a class' namespace.

Static methods that are really just utility or helper functions don't care about the context on which they are called. They just happen to be called on a class as a context because they are assigned as properties on a class. Take, for example, the Uize.Fade.blendValues static method of the Uize.Fade class. You may be calling it on the Uize.Fade context, but this utility function's implementation doesn't even reference the this keyword - it just doesn't care about the context on which it is called. You could, in fact, assign this static method to a local variable and then call it using that local variable, and it would behave in just the same way as if it were being called on the class. Consider the following example...

EXAMPLE

var blendValues = Uize.Fade.blendValues;
alert (blendValues (50,100,.5));  // alerts the text "75"

In the above example, our local variable blendValues is a function reference. When that local variable is used to call the function in order to blend the values 50 and 100, everything works just fine. The alert statement alerts the result of blending 50 and 100 in equal amounts, which is the 75. Because the Uize.Fade.blendValues static method doesn't operate on a Uize.Fade subclass or need a Uize.Fade subclass as its context when it is called, there's no compelling reason for it to be inheritable, and so it is not.

7.4. When Statics Should Be Inheritable

Whenever a static method or property is intended to be unique to its class context, and it is also desirable for that method or property to be inherited by subclasses, then it should not be registered as non-inheritable.

Examples of some static methods in the Uize.Class base class that are inheritable are the Uize.Class.set, Uize.Class.get, Uize.Class.toggle, Uize.Class.fire, Uize.Class.wire, Uize.Class.subclass, and Uize.Class.registerProperties methods. All of these methods act on the class context. So, for example, calling Uize.Class.subclass is different to calling Uize.Widget.subclass. The Uize.Class.subclass method, which is inherited by all Uize.Class subclasses, uses the context of the class on which it is called. Calling the Uize.Class.subclass method returns a subclass of Uize.Class, while calling the Uize.Widget.subclass method returns a subclass of Uize.Widget - even though both of these methods reference the same function for their implementation (in other words, the statement alert (Uize.Class.subclass == Uize.Widget.subclass) would alert the text "true").

7.5. Inheritability of Static Features is Noted in Module References

All static features of modules of the UIZE JavaScript Framework are noted as being either inheritable or non-inheritable in the reference documentation for those features.

The inheritability of the static features of a module is noted in the IMPLEMENTATION INFO notes of the reference documentation for the features in the module's reference. So, for example, if you went to the reference documentation for the Uize.Class.set static method of the Uize.Class base class, you would see it noted that the feature is inherited by subclasses. In contrast, if you went to the reference documentation for the Uize.Fade.blendValues static method of the Uize.Fade class, you would see it noted that the feature is not inherited by subclasses.

8. Phases of Construction

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

8.1. alphastructor

The alphastructor is a function that is executed before the state 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.Class.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 state properties are initialized. This allows onChange handlers for the state 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 state properties being initialized to the values passed to the constructor.

EXAMPLE

var
  MyClass = Uize.Class.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 state property, the alert inside the alphastructor will display the value undefined. This is by design and is an important nuance to take note of.

8.2. omegastructor

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

9. Dissecting a Subclass Module

9.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.

9.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.isWired) {
    // do the wiring stuff

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

9.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 state properties, as in...

_class.registerProperties ({
  // profiles for the state properties
});

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

return _class;

9.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
};

9.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.

9.6. Assigning a Static Method

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

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

In cases where you wish a static method to not be inheritable by subclasses of your class, you can register the method as non-inheritable as follows...

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

For a more in-depth discussion, see the section Non-inheritable Statics.

9.7. Registering State Properties

State properties are registered for a class by calling the MyClass.registerProperties static method that is inherited from the Uize.Class base class.

The MyClass.registerProperties static method lets you register one or more state properties in a single call to the method. The single parameter that the MyClass.registerProperties static method takes is an object containing profiles for all the state properties being registered, where each key is the private name for a state property, and where each value is either the public name - or an object providing a complete profile - for the state 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 state 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 state property, there is no onChange handler registered, and the initial value is undefined. By contrast, the property2 state property is registered with a profile object that specifies an onChange handler function and an initial value of 0.

10. Singletons

The UIZE JavaScript Framework provides support for singletons in the form of the Uize.Class.singleton static method that is implemented in the Uize.Class base class and is inherited by all Uize.Class subclasses.

10.1. Every Class Supports Singletons

Because singleton support is implemented in the Uize.Class base class, a singleton for any Uize.Class subclass can be created by calling the singleton static method on the class.

EXAMPLE

var fileSystem = Uize.Services.FileSystem.singleton ();

In the above example, the Uize.Services.FileSystem class is a subclass of the Uize.Service class, which is itself a subclass of the Uize.Class base class. Therefore, a singleton can be created using the Uize.Services.FileSystem.singleton static method that is inherited from Uize.Class.

10.2. A Single Singleton

As expected with singletons, multiple separate calls to the singleton static method will return a reference to the same singleton.

EXAMPLE

var
  fileSystemSingleton1 = Uize.Services.FileSystem.singleton (),
  fileSystemSingleton2 = Uize.Services.FileSystem.singleton ()
;
alert (fileSystemSingleton1 === fileSystemSingleton2);  // alerts "true"

In the above example, both the fileSystemSingleton1 and fileSystemSingleton2 variables are set to be a singleton of the Uize.Services.FileSystem class. Therefore, when their values are compared using a strict equality, they are found to be equal.

10.3. Compelling Use Cases for Singletons

A typical and compelling use case for the singleton feature is with service classes, where one typically wants to share a single instance of a service class amongst multiple disparate users of the service.

In the case of service classes, one may instantiate and set up the singleton of a service in the environment (which will likely be a Web page for services that are to run in Web applications). Then, in other code that is to run in the environment and use the service, the singleton of the service can simply be obtained and used - the service doesn't need to be initialized by all the users because the same shared singleton of the service class is initialized and set up in the environment.

10.4. Singleton Scope

As a convenience, the singleton static method provides a way to optionally create singletons in a custom scope.

A scope for a singleton of a class is specified using the optional scopeSTR parameter, as follows...

EXAMPLE

var fileSystemForBuildScripts = Uize.Services.FileSystem.singleton ('buildScripts');

In the above example, a singleton of the Uize.Services.FileSystem class is being created in a scope we have decided to call "buildScripts". Now, any other build script code that wants to get a reference to this singleton can perform the same operation, specifying 'buildScripts' as the value for the scopeSTR parameter. With this kind of arrangement, other code can now use the same Uize.Services.FileSystem service class in a different scope. In different scopes, the different singletons may be set up with different service adapters and initialized differently to perform different I/O.

10.4.1. A Single Singleton Per Scope

The exception to the rule of a single singleton is when using the singleton scope feature.

EXAMPLE

var
  fileSystemInScopeA = Uize.Services.FileSystem.singleton ('scopeA'),
  fileSystemInScopeB = Uize.Services.FileSystem.singleton ('scopeB')
;
alert (fileSystemInScopeA === fileSystemInScopeB);  // alerts "false"

In the above example, two singletons of the Uize.Services.FileSystem class are being created: one in a scope we've called "scopeA" and the other in a scope we've called "scopeB". When the values of the fileSystemInScopeA and fileSystemInScopeB variables are compared using a strict equality, they are found to not be equal. However, if we called the singleton method multiple times for "scopeA", we would always get back the same singleton for this scope, and similarly with "scopeB".

10.4.2. Designing Classes to Support Singleton Scope

In order to be able to take advantage of the singleton scope feature, your classes need to be designed correctly.

In order to correctly support singletons in different scopes, your class should not store state for a singleton as statics on the class, but should instead use instance properties. Provided your classes are designed correctly, you can have multiple singletons instantiated for different scopes that you define, so that different sets of distributed code can share their own singleton instances, and for each differently scoped singleton the singleton may be initialized or set up in a different way.

10.4.3. Singleton Scope is Effectively Global

A singleton scope is effectively global for a given class.

Therefore, if you have distributed code that is making use of the singleton feature and you are specifying a custom scope, you need to have a good degree of confidence that you won't have totally different code trying to use the same scope name for a singleton of a class where the initialization and usage of the singleton is different. This is the age old problem with things that are global in nature. The globalness of singletons is both the upside and the downside, so you should use the singleton feature sparingly.

10.5. Initial State for Singletons

A singleton can be created with an initial state by specifying the optional propertiesOBJ second parameter.

EXAMPLE

var fileSystemForBuildScripts = Uize.Services.FileSystem.singleton ('',{defaultEncoding:'utf8'});

In the above example, a singleton is being created for the Uize.Services.FileSystem class. Because the first parameter of the singleton method is the singleton scope, if we want to use the default scope we need to specify an empty string. The second parameter is the state for the singleton, where the value is an object that will be passed to the constructor of the class if the singleton has not yet been created for the scope, or used in a call to the set method if the singleton already exists in the scope.

10.6. Use the Singleton Feature Sparingly

While there are a few compelling use cases for singletons, one should nevertheless use the singleton feature sparingly and with caution.

The very advantage of singletons, which is that they can be shared by disparate pieces of code, can also become a disadvantage. Singletons can be regarded as being, in effect, globals in the scope of the class on which they are created, or the singleton scope in which they are created. This consequence brings with it the kinds of drawbacks that globals have more generally. Singletons aren't as egregious as global variables, and the singleton scope facility goes some way to alleviating the global nature of singletons. Nevertheless, before resorting to using singletons, consider first if there are other alternatives. In UIZE, you will find singletons mostly advocated for use with service classes.