score.oop

This module provides helpers for supporting an object oriented programming style, which is our preferred way of programming in javascript. Several implementation details are heavily dependent on personal taste, which means that it is well possible that you might encounter some alien definitions.

The whole module tries to mimic classes in python for two reasons:

  • we are also python developers, and
  • python, the programming language, has much more elegant design decisions than javascript when it comes to OOP [1].

Creating Classes

The basic usage of the module is as follows:

var Animal = oop.Class({

    __init__: function(self) {
        console.log('Animal constructor')
    }

});

This call creates a trivial class that will log a debug message into the console whenever the it is instantiated. Note that the first argument to all functions is always the current object, i.e. this. This saves us a lot of time, as the usual var self = this; declaration is sometimes forgotten.

The object passed to the function oop.Class is stored unmodified as the __conf__ property of the class. This means that the following code will print true:

var conf = {

    __init__: function(self) {
        console.log('Animal constructor')
    }

};
var Animal = oop.Class(conf);
console.log(Animal.__conf__ === conf);

A best practise is to provide a class name as __name__, which will then be available as an attribute to the class:

var Animal = oop.Class({
    __name__: 'Animal',

    __init__: function(self) {
        console.log(self.__class__.__name__ + ' constructor')
    }

});

Attributes

You can define the default values of attributes as well. Such values are specific to each object, meaning that the following code will print two distinct speed values:

var Animal = oop.Class({
    __name__: 'Animal',

    __init__: function(self) {
        console.log('Animal constructor')
    },

    speed: 10

});

var a = new Animal();
a.speed = 9;
var b = new Animal();
a.speed = 8;
console.log(a.speed); // 8
console.log(b.speed); // 10

Sub-Classing

In order to create a sub-class, you only need to pass the configuration value __parent__:

var Animal = oop.Class({
    __name__: 'Animal',

    __init__: function(self) {
        console.log('Animal constructor')
    }

});

var Bird = oop.Class({
    __name__: 'Bird',
    __parent__: Animal,

    __init__: function(self) {
        console.log('Bird constructor')
        self.__super__();
    }

});

var swallow = new Bird();
swallow instanceof Animal;
swallow instanceof Bird;

The above code will call the child constructor, followed by the base constructor. As you can see, the matching function of the base class can always be accessed via self.__super__(). This is true for all member functions, not just the constructor:

var Animal = oop.Class({
    __name__: 'Animal',

    carry: function(self, object) {
        console.log('Carrying ' + object);
    },

    drop: function(self, object) {
        console.log('Dropping ' + object);
    }

});

var Bird = oop.Class({
    __name__: 'Bird',

    speed: 10,

    carry: function(self, object) {
        self.__super__(object);
        self.speed = 1;
    },

    drop: function(self) {
        self.__super__();
        self.speed = 10;
    }

});

var swallow = new Bird();
swallow.carry('coconut');

Static Attributes

You can assign static values to the class (in contrast to the objects of the class) by passing another configuration value called __static__:

var Animal = oop.Class({
    __name__: 'Animal',

    __static__: {
        minSpeed: 9,
        maxSpeed: 11
    }

    __init__: function(self) {
        var cls = self.__class__;
        var diff = cls.maxSpeed - cls.minSpeed;
        self.speed = (int) (Math.random() * diff) + cls.minSpeed;
    }

});

console.log(Animal.minSpeed); // 9
console.log(Animal.maxSpeed); // 11

var a = new Animal();
console.log(a.minSpeed); // undefined

All non-function values of the __static__ object will only be accessible through the class itself. Functions, on the other hand, will be accessible through instances of the class, too. Note that the first parameter to static functions is always the class itself:

var Animal = oop.Class({
    __name__: 'Animal',

    __static__: {
        minSpeed: 9,
        maxSpeed: 11,
        randomSpeed: function(cls) {
            var diff = cls.maxSpeed - cls.minSpeed;
            return (int) (Math.random() * diff) + cls.minSpeed;
        }
    }

    __init__: function(self) {
        self.speed = self.__class__.randomSpeed();
    }

});

console.log(Animal.randomSpeed()); // 9
console.log(Animal.randomSpeed()); // 10

var a = new Animal();
console.log(a.randomSpeed()); // 11

The cls parameter will receive the class the static function was called on, not the one it was defined in. The following code uses different min and max values for the same calculation, for example:

var Snail = oop.Class({
    __name__: 'Snail',
    __parent__: Animal,

    __static__: {
        minSpeed: 0,
        maxSpeed: 1
    }

});

var otto = new Snail();
console.log(otto.speed); // 1

Events

It is possible to mark a class as an events source by providing an __events__ configuration:

var Animal = oop.Class({
    __name__: 'Animal',
    __events__: ['running', 'stopping'],

    run: function(self) {
        self.trigger('running', self.speed);
    },

    // ....

});

var otto = new Snail();

otto.on('running', function(speed) {
    console.log('Otto started running at speed ' + speed);
});

Providing a list of possible __events__ creates the two methods on and trigger. The trigger function just needs the name of an event, but all additional arguments will be delegated to the callback functions registered with on. The context of the callback (i.e. the this value) is always the object triggering the event.

Both functions will throw an Error if the provided event name was not configured. It is possible to allow arbitrary events on a class by setting the __events__ value to True:

var Dog = oop.Class({
    __name__: 'Dog',
    __parent__: Animal,
    __events__: true
});

var otis = Dog();

otis.on('bork', function() {
    console.log('Not barking!');
});

Static Events

It is also possible to configure event handling at the class level. In such cases, the context of these callbacks is the class:

var Animal = oop.Class({
    __name__: 'Animal',

    __static__: {
        __events__: ['create'],
    }

    __init__: function(self) {
        Snail.trigger('create', self);
    }

});

Animal.on('create', function(snail) {
    console.log('Created new ' + this.__name__);
});

var Snail = oop.Class({
    __name__: 'Snail',
    __parent__: Animal

});

new Snail(); // prints "Created new Snail"

Footnotes

[1]This is mostly because javascript evolved under the influence of various browser vendors and committees with very few opportunities for breaking changes, whereas python has a benovelent dictator and has undergone radical rewrites in the past.