Archive

Archive for May, 2014

OO design and classes in JavaScript

2.05.2014 Leave a comment

JavaScript is a language which has a lot of crufty syntax, but underneath the cruft it has many useful features.

One of the problems people encounter when coming to JavaScript with experience from other languages, is that there are no classes in JavaScript.

The rest of this post assumes you have basic knowledge of JavaScript.

Here are two basic ways to create objects in JavaScript:

// The most common way - using constructor function
function Point(x, y)
{
    this.x = x;
    this.y = y;
}
var p = new Point(1, 2);

// Using a create function
function CreatePoint(x, y)
{
    return { x: x,
             y: y };
}
var p = CreatePoint(1, 2);

This is not exactly object-oriented programming, is it? Let’s say we stick with it, how do we introduce inheritance? JavaScript has prototypal inheritance, which is not how most developers understand inheritance. Let me give you an example:

// The prototype
function Base(a)
{
    this.a = a;
    this.print = function() {
        console.log("a=" + this.a + ", b=" + this.b);
    };
}
var proto = new Base(0);

// Usable constructor
function Derived(b)
{
    this.b = b;
}
Derived.prototype = proto;

// Classic approach to adding more members to the prototype
Derived.prototype.hello = function() {
    console.log("Hello!");
};

var o1 = new Derived(1);
var o2 = new Derived(2);

o1.print(); // prints: a=0, b=1
o2.print(); // prints: a=0, b=2

proto.a = -1;

o1.print(); // prints: a=-1, b=1
o2.print(); // prints: a=-1, b=2

First observation: objects created by the Derived constructor share the same instance of the prototype, not a copy. If the prototype object changes, all objects which use this prototype see these changes.

Second observation: the base “class” is non-customizable from the Derived constructor. We don’t call the Base constructor from the Derived constructor. One workaround would be to add an Init function in the prototype, which would set some members of the object.

Third observation: if we have lots of functions and members in the base “class”, prototypal inheritance can in theory save on memory (the same members are not duplicated across all instances).

Fourth observation: there is no such thing as private properties.

“Real” classes in JavaScript

To the contrary of what most people think, constructors may be used the same way as classes are used in other OO languages.

The key to taking JavaScript to the next level are closures. Closures are variables accessible across functions. When an inner function (defined inside another function) accesses a variable of the outer function, that variable becomes a closure. In JavaScript a very interesting thing happens with closures: they survive the end of the function which declared them and are still usable in the inner functions which access them.

Let’s get on with it: Here is an idiom which lets us create classes in disguise using constructor functions, just like in any other OO language.

// Class (constructor function)
function Rectangle(x1, y1, x2, y2) // Constructor arguments
{
    // Private variables
    var w = x2 - x1;
    var h = y2 - y1;

    // Note: reuse arguments as members!

    // Public functions
    this.getWidth = function() {
        return w;
    };
    this.getHeight = function() {
        return h;
    };
    this.getArea = function() {
        return this.getWidth() * this.getHeight();
    };

    // Accessor
    this.getX1 = function {
        return x1;
    };
}

// Usage
var o = new Rectangle(1, 1, 3, 4);
console.log(o.getArea()); // 6

What about inheritance? Easy:

function Square(x, y, size)
{
    // Call base class constructor on this object
    Rectangle.apply(this, x, y, x+size, y+size);

    // Other Square-specific members follow...
}

var s = new Square(1, 1, 2);
console.log(s.getArea()); // 4

Last but not least, this is a very useful idiom to complete member functions later, handy in user interfaces:

function delay(time, func)
{
    window.setInterval(func, time);
}

function SomeObject(x)
{
    // Save 'this' for lambdas
    var self = this;

    var v = x * x;
    this.publicV = x * x * x;

    this.getValue = function() {
        return v;
    };

    // Private member
    var alterValue = function(newx) {
        v = newx * newx; // access private variable
        self.publicV = newx * newx * newx; // access 'this' via 'self'
    };

    this.setValues(x1, time, x2) {
        alterValue(x1)
        delay(time, function() {
            // 'this' is bound to something else in a lambda function,
            // use self instead
            alterValue(x2);
        });
    };
}

var o = new SomeObject(2);
console.log(o.getValue());     // 4
o.setValues(5, 1000, 6);
console.log(o.getValue());     // 25

// Wait >1 seconds, because after at least one second, the value will change again
delay(2000, function() {
    console.log(o.getValue()); // 36
});

Summary

In effect, the above approach works like classes in other object-oriented languages. Arguably, it’s cleaner than a typical prototype-based approach in which one assigns members to a prototype outside of the constructor function – the guts of the object in a typical prototype-based approach are scattered around the source file(s).

I haven’t measured the performance of the above approach versus a prototype-based approach, but my gut feeling is that modern JavaScript engines deal with it comparably well.

Advertisements
Categories: Computing