Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Typescript

How to Build Application Libraries in JavaScript using Encapsulation, Inheritance and Polymorphism with jTypes

5.00/5 (1 vote)
9 Jun 2013Apache6 min read 9.8K   68  
A quick tutorial on how to define classes in jTypes

Introduction

jTypes is the most comprehensive and robust JavaScript library for overcoming differential inheritance with prototype-based objects. Its lightweight yet powerful design provides web programmers on any platform or browser the ability to emulate classical inheritance where objects are defined by classes.

Download the latest version at www.jTypes.com (2.0.2 at the time of this writing).

Background

If you're looking for a more down-to-earth explanation rather than the generic description or tagline, then here's the deal: jTypes will provide you with the ability to develop robust, modular, and scalable application libraries in JavaScript using encapsulation, inheritance, and polymorphism.

What exactly does that mean? You know all those keywords from languages such as C++ or C# that you started missing quite badly after you transitioned to JavaScript? You know what I'm talking about. All those beautiful modifiers of classical inheritance such as virtual, abstract, and override or private, protected, and public that gave you so much more control and freedom with your libraries. Well, jTypes lets you take those keywords back, so you can develop extremely powerful and robust web applications using the principals of classical inheritance.

Using the Code

The $$ global variable is used as a shorthand for the jTypes global variable. Classes are defined using the jTypes compiler and then instantiated as shown in the following definitions:

JavaScript
var SomeClass = $$([String modifiers,] [Class baseClass,] 
[Function constructor,] Object prototype); 
JavaScript
var someInstance = new SomeClass(); 

In the following example, a class is compiled and the type reference is stored in the Person variable. A constructor is provided to take in three arguments and set the corresponding fields. Two public read-only fields are defined using the readonly modifier and public access modifier, and a protected field is defined as well using the protected access modifier. A public method is defined along with a virtual public method using the virtual modifier. Finally, a property with both get and set accessors is provided:

JavaScript
var Person = $$(function($fName, $lName, $age)
{
    this.firstName = $fName;
    this.lastName  = $lName;
    this._age = $age;
},
{
    'public readonly firstName': '',
    'public readonly lastName': '',
    'protected _age': 0,
 
    'public getFullName': function()
    {
        return this.firstName + ' ' + this.lastName;
    },
    'public virtual triggerOneYearOlder': function()
    {
        this._age++;
    },
 
    'public age':
    {
        'get': function()
        {
            return this._age;
        },
        'set': function($v)
        {
            if ($v > 0)
                this._age = $v;
        }
    }
}); 

Constructors

Constructors are special methods that are executed when an object is created. They provide a means of initializing the fields and/or properties of the new object. A constructor is called only once during instantiation and must be invoked using the new keyword when creating the object. The base constructor may be accessed through this.__base (note the double underscore).

Fields

Fields are variables declared directly in a class. They store data that must be accessible to multiple methods or properties. Classes may have instance fields or static fields using the static modifier. Instance fields must be declared using the types undefined, null, boolean, number, or string. Any other type is automatically treated as null with the exception of function (which is used to define methods) and "simple object" (which is used to define properties). To set a field to any "reference-type" object, you must do so in the constructor to prevent cross-referencing. Instance fields may also contain the readonly modifier, which prevents the value of the field from being altered outside of the constructor.

Methods

All variables of type function are treated as methods unless they have the static modifier. The JavaScript reserved word this holds reference to the private instance of the object. However, the public instance of the object may be accessed through this.__this. Subsequently, the protected base class instance of the object may be accessed through this.__base (note the double underscores on each). Method declarations can also contain the virtual, abstract, override, and sealed modifiers to implement method overriding or forcing derived classes to write their own implementation of a method.

Properties

All variables of type "simple object" are treated as properties unless they have the static modifier. Properties combine aspects of both fields and methods. They are used as if they are field members, which allows data to be easily accessed, yet they maintain the safety and security of methods. The property definition must contain either one or two special methods known as accessors. A get accessor has no arguments and should return a value for the property when being accessed. A set accessor has one argument, which is the incoming value being set to the property, and need not return a value. If only a get accessor is provided in the definition, the property acts as a read-only field, while if only a set accessor is provided in the definition, the property acts as a write-only field. If both accessors are provided, the property acts like a normal field member, and one accessor may specify a more restrictive access modifier as well using the private, protected, or public access modifiers. Property declarations can also contain the virtual, abstract, override, and sealed modifiers to implement property overriding or forcing derived classes to write their own implementation of a property.

In the following example, a class is derived from the Person class and the type reference is stored in the Employee variable. Four arguments are provided to the constructor, with which the first three arguments are passed into the base constructor, while the final argument is used to set a protected field defined in the class. A public virtual method that overrides the base virtual method is defined. This method also calls the base virtual method. Finally, a property with only a get accessor is provided, which will act as if it is a read-only field:

JavaScript
var Employee = $$(Person, function($fName, $lName, $age, $salary)
{
    this.__base($fName, $lName, $age);
    
    this._salary = $salary;
},
{
    'protected _salary': 0,
 
    'public override triggerOneYearOlder': function()
    {
        this.__base.triggerOneYearOlder();
 
        this._salary *= 1.03;
    },

    'public salary':
    {
        'get': function()
        {
            return this._salary;
        }
    }
}); 

Once the jTypes classes are compiled and instantiated, they can be type-checked and casted using the following constructs:

JavaScript
someInstance instanceof SomeClass 
JavaScript
someInstance.as(SomeClass) 

Type-Checking

Type-checking can be performed using the instanceof operator as in the example shown above. Even when an object is down-casted, it can still be checked for its original type using the instanceof operator. Variables can also have their native JavaScript types checked using the $$.type(obj) helper. This function will always return a string from the following array of types: array, boolean, class, date, function, instance, number, null, object, regexp, string, undefined, window. Finally, there are also helper functions for each type from the previous array such as $$.isArray(obj), $$.isDate(obj), $$.isFunction(obj), $$.isInstance(obj), and so on with the exception of object, which has the $$.isSimpleObject(obj) helper.

Casting

Casting can be performed using the .as(obj) method attached to every class instance as in the example shown above. There are also helper functions for a few common casting scenarios encountered when working with function arguments such as $$.asArray(obj), $$.asBool(obj), $$.asFloat(obj), $$.asInt(obj), and $$.asString(obj).

In the following example, both classes defined in this tip will be instantiated and tested to ensure their functionality is correct based on their jTypes definitions:

JavaScript
// instantiate a person object
var p = new Person('John', 'Doe', 30);

// check that the values were set
console.log(p.firstName);// John
console.log(p.lastName);// Doe
console.log(p.age);// 30

// get a protected field
console.log(p._age);// undefined

// set a readonly field (throws in strict mode)
p.firstName = 'Jane';

// set an invalid property value (throws if you code it)
p.age = -40;

// check that the field and property didn't change
console.log(p.firstName);// John
console.log(p.age);// 30

// set a valid property value
p.age = 40;

// check that the property did change
console.log(p.age);// 40

// invoke a method
console.log(p.getFullName());// John Doe

// invoke a virtual method
p.triggerOneYearOlder();

// check that the age was incremented (by the virtual method)
console.log(p.age);// 41

// instantiate an employee object
var e = new Employee(p.firstName, p.lastName, p.age, 75000);

// check that the inherited values were set
console.log(e.firstName);// John
console.log(e.lastName);// Doe
console.log(e.age);// 41

// get an inherited protected field
console.log(e._age);// undefined

// set an inherited readonly field
e.firstName = 'Jane';

// check that the field didn't change
console.log(e.firstName);// John

// get a declared field (not inherited)
console.log(e.salary);// 75000

// cast the employee object as a person object
e = e.as(Person);

// check the types of both person objects
console.log(p instanceof Person);// true
console.log(p instanceof Employee);// false
console.log(e instanceof Person);// true
console.log(e instanceof Employee);// true

// check that both person objects don't have the derived property
console.log(p.salary);// undefined
console.log(e.salary);// undefined

// invoke an overridden method
e.triggerOneYearOlder();

// cast the person object as an employee object
e = e.as(Employee);

// check that the age was incremented (by the base method)
console.log(e.age);// 42

// check that the salary increased 3% (by the overridden method)
console.log(e.salary);// 77250  

All source code in this example, along with a copy of jTypes 2.0.2, can be obtained from the download link at the top of this tip.

Points of Interest

The $$.empty() helper function will return a new empty function reference each time it is invoked. Another helper function has been provided for formatting error messages when throwing exceptions using the following syntax:

JavaScript
throw $$.format(String message, Object arg0, ...); 

There have also been significant performance increases in the upcoming beta:

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0