Classes and Object Instances

JavaScript is an object-oriented language that uses a prototype-based mechanism for creating classes and object instances. This is somewhat unusual and does take some getting used to. It creates classes by means of constructor functions to create and configure for object instances. After years of experimenting, certain best practices have evolved to support creating classes, instances, overriding methods and controlling member visibility.

Ejscript enhances standard JavaScript by providing classical class and interface directives to simplify the creation of classes and instance objects. This mechanism is compatible with the standard prototype based inheritance. It is your choice which mechanism you choose to use, you can even mix and match techniques. If you prefer traditional prototype-based inheritance, then use that. If you prefer the simpler, more classical class-based inheritance, go for it.

Prototype Inheritance

Prototype Inheritance uses constructor functions to create and initialize instances. The object instance is created via the new operator. Given a constructor called Rectangle, the following statement will create a new Rectangle instance.

var rect = new Rectangle(10, 20)

When the new statement is executed, it creates a new bare object and passes that object to the Rectangle constructor as the this object.

The Rectangle constructor's job is to configure the object instance.

Instance Properties

Instance properties are created by simple assignment in the constructor function.

function Rectangle(width, height) {
    this.width = width
    this.height = height
}

In this example, the Rectangle constructor function creates two new instance properties height and width by assigning initial values to them. Because the constructor function is run each time a new instance is created and this is bound to the new objects — this will effectively create and initialize instance properties.

Behind the scenes, Ejscript optimizes object creating by using a technique called Object Shaping, where the VM creates and updates hidden classes to represent the object instances.

Methods

Although functions can be assigned to instance properties in the constructor function, there is a better way to create methods by using the constructor function's prototype object.

Every function in JavaScript has a prototype object which is like a hidden class object for the constructor. The prototype object contains properties that will appear in every instance created by the constructor and is the ideal place to create methods and class constants that you want to share between all instances.

Rectangle.prototype.area = function() {
    return this.width * this.height
}

This creates a method called area that will be accessible from Rectangle instances. Note that when you add a function to the prototype it is added to all instances — even pre-existing instances.

print(rect.area())

Properties are not copied from the prototype object into instances. Rather, the property lookup mechanism will search the prototype object if a named property is not found in the instance itself. This significantly reduces the memory required to store objects when multiple instances of a class are being created.

If a prototype property is updated by an instance, the VM will update the object instance which will then effectively shadow or replace the prototype property.

Class Methods

To create a class method, simply attach a function to the constructor function.

Rectangle.makeSquare = function(length) {
    return new Rectangle(length, length)
}

This creates a class factory method which creates squares.

Class Properties

To create class properties or constants that are shared across all instances, use the same technique as that used for methods: create the property via the constructor prototype object.

Circle.prototype.PI = 3.1415926

This creates a shared property in the Circle class for PI.

Visibility

JavaScript has techniques to create private properties that are visible only to the methods of the class. Declare a local variable inside the function constructor to create a private property.

function Circle(radius) {
    var area = 3.14 * radius * radius
    this.radius = radius
}

To access radius in other methods, requires defining the method inside the constructor as a closure. Closure functions capture their outer scope and thus have access to the private variables of the constructor.

function Circle(radius) {
    var area = 3.14 * radius * radius
    this.radius = radius
    this.getArea = function() { 
        return area
    }
}

Inheritance

To implement inheritance into your classes, we exploit the prototype object again. The constructor's prototype object itself has a prototype which is by default Object.prototype. However, this can be modified to point to another class and thus create a prototype chain.

When searching for a property, if the VM fails to find the property in the object instance, it will search the object's prototype property. If it is not found on the prototype, the VM searches the prototype's prototype, and so on, until Object.prototype is searched. This creates a lookup and inheritance chain.

This next example will create a Square class by extending the Rectangle class we previously created.

function Square(length) {
    /* Call the rectangle base class */
    Rectangle.call(this, length, length)
}
/* Set the base class for Square */
Square.prototype = new Rectangle
/* Reset the constructor for Square */
Square.prototype.constructor = Square
Square.prototype.isSquare = function() { return true; }
var square = new Square(10)
/* Invoke Rectangle.getArea() */
print(square.getArea())

Yes I know this is not pretty, this is why we added class-based inheritance to Ejscript. It is much easier and more intuitive. There are also other alternative approaches — all with their strengths and weaknesses.

Alternate Approaches

There are many patterns for creating classes and doing inheritances. JavaScript is amazingly flexible. Here are a some links to alternative approaches.

Class Inheritance

Because the standard JavaScript Prototypal Inheritance mechanism is a little tedious to work with, Ejscript adds a classical class based inheritance facility. It is syntactic sugar over the prototype inheritance, but also it has support in the VM to provide higher performance than that offered by pure Prototypal Inheritance.

Class inheritance uses the class keyword to define classes.

class Rectangle {
    var     height
    var     width
} 

This creates a simple class object called Rectangle. This is loosely equivalent to the prototype object used in Prototypal Inheritance. Instances can now be created by using new.

var rect = new Rectangle

Instance Properties

This creates a new Rectangle object and sets its hidden prototype object to point to the Rectangle class. It also automatically creates the instance properties: height and width. However, they are not yet initialized. We really need to provide a constructor function so we can define the height and width values of the rectangle.

class Rectangle {
    var     height
    var     width
    function Rectangle(h, w) {
        height = h
        width = w
    }
}

This time, when new Rectangle is invoked, the constructor will initialize height and width. Notice that Rectangle must be defined inside the class block and that it has access to the private height and width variables. If we want to make height and width visible to users outside the class, then we add a public qualifier to the declaration.

public var height

Methods

To create methods, you add functions to the class block just as we did for the constructor. If you want the function to be visible outside, add a public qualifier.

class Rectangle {
    /* Portions omitted */
    public function area() {
        return height * width
    }
    /* Private function */
    private function grow() {
    }
}

Class Methods

Class methods are created by using the static qualifier. Class (or static) methods do not have access to any instances. When they run, the this property is set to the class object itself.

class Rectangle {
    public static function makeSquare(length) {
        return new Rectangle(length, length)
    }
}

Class Properties

Class properties that are shared across all instances are created similarly to class methods by using a static qualifier.

class Rectangle {
    public static var goldenRation = 1.618034
}

Visibility

Controlling visibility is achieved by using the internal, public, private and protected qualifier keywords. Class members can be made visible throughout the source file in which they are declared by using the internal qualifier. This is the default visibility for a declaration if no qualifier is used.

To make a declaration visible outside the source file, use public on your declarations.

To make class members private and only visible inside the class, use the private qualifier. To permit sub-classes to access members, but deny access to code outside the class, use the protected qualifier.

Visibility can also be controlled on a module basis by using Module directives. See the Modules and Namespaces document for details.

Inheritance

To inherit or sub-class a class, use the extends keyword.

class Square extends Rectangle {
    function Square(length) {
        super(length, length)
    }
}

This example extends Rectangle to create a derived class Square. The Square constructor invokes the Rectangle constructor via the super pseudo-property.

Early Binding

The Ejscript compiler performs early-binding. This means it searches for properties and resolves whether they reside in the object instance, class or sub-classes. Once found, the compiler generates direct references to the property and avoids the run-time scanning of the prototype chain. This is typically dramatically faster than runtime property lookup.

Reflection

Ejscript provides a set of operators and classes to enable you to determine the type of an object and reflect upon its members.

The JavaScript standard typeof operator can be used to test the type of an object. It returns a string that describes the object type.

print(typeof 5)
print(typeof "Hello World")
/* Emits */
number
string

Unfortunately, the typeof operator has its quirks and doesn't behave as expected in some cases. It returns "boolean" for booleans, "function" for functions, "number" for numbers, "string" for strings and "undefined" for undefined values. So far so good. However, It returns "object" for objects, arrays, dates, regular expressions and null. Further, if you use a Number, String or Boolean wrapper object, it also evaluates to "object". This makes typeof much less useful. Ejscript provides a more predictable reflection capability that fixes these deficits.

The instanceof operator tests whether an object is an instance of a class object (or function constructor).

rect = new Rectangle
print(rect instanceof Rectangle)

Reflect Class

The Reflect class is an Ejscript extension that provides more consistent and comprehensive class and object introspection facilities. It provides an out-of-band reflection capability and does not pollute the class and objects themselves with additional properties or methods.

The Reflect class provides a function constructor that can be used without new to return a reflection object. This reflection object can then be used to examine the target object.

var rect = new Rectangle(1, 10)
examine = Reflect(rect)
print(examine.typeName)
/* Emits */
Shape

Interfaces

Interfaces are used to create contracts for classes. They specify the required methods (or contract) the class must implement to validly support a given interface. These are very similar to ActionScript or Java interfaces.

interface Shape {
    function render()
    function getArea()
}

© Embedthis Software. All rights reserved.