Blog Home  Home Feed your aggregator (RSS 2.0)  
Microsoft AJAX Library (Atlas) – Javascript OOP enhancements (Part 1) - Manuel Abadia's ASP.NET stuff
 
# Wednesday, 27 September 2006

I have started to look at Atlas. The first thing I have played with is the layer to provide better OOP features for Javascript.

Note: this is based on an early version of the product (July CTP), so this information can change for the final release.

Javascript OOP features

Javascript is a prototype-based object oriented language. This means that every object has a prototype, an object from which it inherits properties and methods. The prototype is a shared property of the class that can be accessed like this:

myClass.prototype

As Javascript is an interpreted language, when the interpreter founds an expression like:

myObject.myProperty

first it checks if the object instance “myObject” has a property called “myProperty”. If not, then it tries to find that property in the prototype object of that class (myClass.prototype.myProperty). This prototype relationship is recursive, because the prototype is an object that can have a prototype, so this recursion ends only when Object is reached, the superclass of all objects.

In Javascript, a class is defined by a function (the constructor) and its prototype (that defines the properties and methods that each object instance has by default, because of how the interpreter evaluates the expressions).

So, to define a class in Javascript we have to do this:

TestClass = function() {

}

If we want to add a method to the class we can do it like this:

TestClass = function() {

    this.method1 = function(){

    }

}

Or like this:

TestClass = function() {

}

 

TestClass.prototype.method2 = function(){

}

 

What's the difference? Consider the following class:

TestClass = function() {

    this.method1 = function(){

    }

}

 

TestClass.prototype.method2 = function(){

}

If we instantiate two objects of the class TestClass, we can see the difference:

var objTestClass1 = new TestClass();

var objTestClass2 = new TestClass();

 

This diagram shows the internal representation of the object:


As you can see, the method declared using the prototype is shared between instances of the class. However, the method added in the constructor is created for each instance. So:

objTestClass1.method1 != objTestClass2.method1
and
objTestClass1.method2 == objTestClass2.method2

Basically, the prototype is the only “object oriented mechanism” present in Javascript. As you can see it is not an usual object oriented language. Using the prototype you can come up with your own solution to simulate a more complete object oriented language (inheritance, virtual methods, calling the base method, etc).

Atlas OOP features

Atlas adds some valuable features to provide a more natural object oriented programming models. What Atlas offers is:
• namespaces
• inheritance
• properties
• defining virtual methods
• calling base methods
• interfaces
• enumerations
• attributes
• reflection

Namespaces

To create a namespace in Atlas you have to write something like:

Type.registerNamespace('Manu.Atlas.Tests');


Internally Atlas adds a Type property to the window object (the default Javascript context), and assigns it the Function built-in type. Atlas adds a lot of methods and properties to Function, as we’ll see later.

The registerNamespace function splits the namespace by the dots and creates a hierarchy of objects in the window object that resemble the namespace. For example, for the namespace Manu.Atlas.Tests the following properties are created:

window.Manu = new Object();

window.Manu.Atlas = new Object();

window.Manu.Atlas.Tests = new Object();


Inheritance

Atlas theorically supports multiple inheritance, but in the July release it isn’t working properly. Anyway, multiple inheritance support will be removed before the final version, so don't count on it.

A class in Atlas has to be registered to work properly. The syntax to register a class is:

FullClassName.registerClass(typeName, baseTypes, interfaces)

Only the first parameter is mandatory. The second parameter specifies the base class or base classes if multiple inheritance is used. The second parameter could be a reference to the base class or an array of type names for the base class (or classes). The third parameter is the interface implemented by the class (if any). A class can implement multiple interfaces so you can call registerClass with more than 3 parameters. Any extra parameter should be an interface that is being implemented by the class.

Atlas adds some information to each class type it creates to provide the necessary machinery for the object oriented features:

• _typeName field: with the name of the type (the function getTypeName returns it).
• _baseType field: a reference to the parent type (the function getBaseType returns.
• _basePrototypePending property: a boolean that is used to know if the class has been initialized (more on this later).
• _interfaces: an array with references to the interfaces implemented by the class.
• bases: an array with references to the parent classes.
• _interface, _sealed and _abstract boolean properties to give information about the type.
• _baseMethods: a hash table where methods that can be called by child classes are stored.

The core of the inheritance mechanism is handled by the initializeBase function that has to be called in the constructor of a derived class. The syntax is:

FullClassName.initializeBase(instance, baseArguments)

Where the first parameter is the instance to be initialized and the second parameter is an array of arguments to pass to the base constructor.

The initializeBase method starts calling the constructor for each interface in the class (stored in the _interfaces collection). Then calls the _setBases method and finally calls the _callBaseConstructors method.

The _setBases method does an important job. If the class inherits from another class, the method iterates through all the base classes, filling the bases collection, and recursively calling the _setBases method on the base classes. After the _setBases method has been called for a base class, the _copyProps method is called. The _copyProps method iterates through all properties and methods stored in the prototype of the base class and stores them in them in the prototype of the subclass. So, after the call to _copyProps, the subclass also has the properties and methods specified in the prototype of the base class.

The _callBaseConstructors method iterates through the bases array, calling the base constructor with the current object instance.

So, after calling the initializeBase method, the object has all the properties and methods of the base classes, giving the illusion of inheritance.

If we declare the methods and properties in the prototype, they will be created in the child classes in the _copyProps method. If we create the properties and methods in the constructor, they will be created in the child classes in the _callBaseConstructors method.

An example of a class B that inherits from A is:

Type.registerNamespace('Manu.Atlas.Tests');

 

Manu.Atlas.Tests.A = function (a1, a2) {

    this._a1 = a1;

    this._a2 = a2;

}

Manu.Atlas.Tests.A.prototype._a1 = 0;

Manu.Atlas.Tests.A.prototype._a2 = 0;

Manu.Atlas.Tests.A.prototype.get_a1 = function () {

    return this._a1;

}

Manu.Atlas.Tests.A.prototype.set_a1 = function(value) {

    this._a1 = value;

    return value;

}

 

Manu.Atlas.Tests.A.prototype.get_a2 = function () {

    return this._a2;

}

Manu.Atlas.Tests.A.prototype.set_a2 = function(value) {

    this._a2 = value;

    return value;

}

 

Manu.Atlas.Tests.B = function (a1, a2, b1) {

    Manu.Atlas.Tests.B.initializeBase(this, [ a1, a2 ]);

    this._b1 = b1;

}

Manu.Atlas.Tests.B.prototype._b1 = 0;

 

Manu.Atlas.Tests.B.prototype.get_b1 = function () {

    return this._b1;

}

Manu.Atlas.Tests.B.prototype.set_b1 = function(value) {

    this._b1 = value;

    return value;

}

 

Manu.Atlas.Tests.A.registerClass('Manu.Atlas.Tests.A', null);

Manu.Atlas.Tests.B.registerClass('Manu.Atlas.Tests.B', Manu.Atlas.Tests.A);


Note that we have created the properties and methods using the prototype in order to save memory. We can declare the A class like this:

Manu.Atlas.Tests.A = function (a1, a2) {

    this._a1 = a1;

    this._a2 = a2;

 

    this.get_a1 = function () {

        return this._a1;

    }

    this.set_a1 = function (value) {

        this._a1 = value;

    }

    this.get_a2 = function () {

        return this._a2;

    }

    this.set_a2 = function (value) {

        this._a2 = value;

    }

}


And it will be functionally equivalent although it will be more expensive in memory terms as each object instance will have 4 methods instead of sharing them (as explained before). This mode of creating a class is called the closure model. The previous one is called the prototype mode.

If we create an object of type B, we can call the base properties. In the following sample, the last line evaluates to 3:

var objA = new Manu.Atlas.Tests.A(2, 7);

var objB = new Manu.Atlas.Tests.B(1, 3, 5);

objB.get_a2();



There are more functions to register a class, depending on the behaviour of the class:
registerSealedClass and registerAbstractClass. The name clearly shows when to use them although they just set a property and call to the registerClass method. If a class is registered using registerSealedClass any class inheriting from it will not have its members copied because the _setBases method will not be called although no error will appear.

The Function built in type is also extended with some methods to check interface implementation, inheritance and type:
• implementsInterface
• isImplementedBy
• inheritsFrom
• isInstanceOfType

Properties

As javascript doesn't have properties like C#, the recommended approach (and what is used in the atlas library) is to provide a getter and a setter method for properties with the signature get_propertyName set_propertyName as we did earlier for the Manu.Atlas.Tests.A class. It is really important that the getter starts with "get_" and the setter with "set_" as this is mandatory for integrating our custom components into the Atlas framework as we will see in a future.

Methods

The initializeBase method provided most of the machinery for the OOP with Atlas, but there is something missing: how to call a base method from an overridden method.

Atlas provides 3 methods related to this problem:
• registerBaseMethod
• callBaseMethod
• getBaseMethod

The first one is saves a method in the _baseMethods hash table, in order for a child class to call to the base class implementation. The child class has to call to the callBaseMethod for this, and the implementation of callBaseMethod uses the getBaseMethod method in order to obtain a reference to the base method.

There is no need to call to the registerBaseMethod if we define our methods using the prototype because the getBaseMethod searchs the method in the prototype if it isn’t found in the _baseMethods hash table.

An example of a class with a method that calls the base method follows:

Manu.Atlas.Tests.C = function (a1, a2, b1) {

    Manu.Atlas.Tests.C.initializeBase(this, [ a1, a2, b1 ]);

}

Manu.Atlas.Tests.C.prototype.get_b1 = function () {

    return Manu.Atlas.Tests.C.callBaseMethod(this, 'get_b1');

}

 

Manu.Atlas.Tests.C.prototype.set_b1 = function (value) {

    Manu.Atlas.Tests.C.callBaseMethod(this, 'set_b1', [ value ]);

}

 

Manu.Atlas.Tests.C.registerClass('Manu.Atlas.Tests.C', Manu.Atlas.Tests.B);

 

The Function built in type is extended with two static functions to help when creating a class or an interface:
• abstractMethod: method that throws an exception if called.
• emptyFunction: the name says it all.

I’m running out of time now so in another post I’ll finish talking about the OO features of Atlas.

Wednesday, 27 September 2006 00:34:38 (Romance Daylight Time, UTC+02:00)  #    Comments [0]   Ajax | ASP.NET | JavaScript  | 
Copyright © 2018 Manuel Abadia. All rights reserved.
DasBlog 'Portal' theme by Johnny Hughes.