JavaScript Patterns

Here I am going to list down some of the programming patterns I have picked from my work with ExtJS 3, YUI 2, JavaScript Patterns book, and from various articles in the internet and with my work on open-source project CopperJS.

Note: 1,2 and 6 are specific to JavaScript, the others can be implemented in other programming languages.

1. Module pattern

Reduce global variables and functions, and also use it for declaring private functions/variables.

var randomColor = (function() {
    var randomHex = function (c) {
        return ((Math.random() * 16)|0).toString(16);
    };
    return function () {
        return "#000000".replace(/0/g, randomHex);
    };
}());
...
var color = randomColor();
Another example (this time totally avoiding any globals):
(function(global) {
    var randomHex = function (c) {
            return ((Math.random() * 16)|0).toString(16);
        },
        randomColor = return function () {
            return "#000000".replace('/0/g, randomHex);
        };

    //In case you need to expose something globally
    global.randomColor = randomColor;
}(this));

2. Argument and context binding

In JS, the context of a function ('this' keyword) can be changed during a call to that function.
For example:

function alertMember() {
    alert(this.val);
}
alertMember.call({
    val: 10
});
alertMember.call({
    val: 11
});
A very common gotcha when using DOM event handlers is context.
function myclass(element) {
    this.val = 10;
    element.onclick = this.onclick;
}
myclass.prototype.onclick = function () {
    alert(this.val);
}

var button = document.getElementById('abutton');
var instance = new myclass(button);
When you click the button, you will get 'undefined' as the alert message. This is because 'this' is not the myclass instance.
If you do an "alert(this)" within the event handler you'll get "[object HTMLButtonElement]" as output (on IE8- you will get "Window" as output).
To get the code working, we need to set (bind) the 'this' keyword to the myclass instance.
function bind(func, context) {
    return function () {
        //Note: arguments is a special variable that contains the arguments to this inner function (when called).
        func.apply(context, arguments);
    };
}

//More sophisticated bind
function bind(func, context, append) {
    var args = Array.prototype.slice.call(arguments, 3);
    return function() {
        //append/prepends binded args to arguments during call.
        var innerArgs = Array.prototype.slice.call(arguments, 0);
        func.apply(context, append ? innerArgs.concat(args) : args.concat(innerArgs));
    };
}
Now you just need to assign the event handler as element.onclick = bind(this.onclick, this);

3. Classes and inheritance

I've dedicated an entire article to this topic - link.

4. Publisher-subscriber

You have seen this pattern, whenever you deal with DOM events. For example, you can register multiple click handlers onto a button or a div. This pattern can be extended to your application specific objects/components.
A component could "listen" to a publisher by registering for a event on the publisher.
For e.g: button.addListener("click", someFunction). Here button is the publisher and someFunction is the listener. When a user clicks the button, an event is fired from it (the publisher) to all the listeners/components that have registered for the event.
With this you can achieve loose coupling between various components of your application. The publisher itself doesn't have to care about what goes on inside the listeners.
This pattern is kinda essential for UI development. It also appears in frameworks outside JS, like in Qt (called signals and slots).

Here is an implementation (not necessarily perfect, but gives you the picture):

var makePublisher = (function () {
    var methods = {
        on: function (eventType, func, context) {
            var eventMap = (this.eventMap = this.eventMap || {}),
                listeners = (eventMap[eventType] = eventMap[eventType] || []);
            listeners.push({
                func: func,
                context: context
            });
        },
        fire: function(eventType) {
            var args = Array.prototype.slice.call(arguments, 1),
                eventMap = this.eventMap,
                i, len, listeners;
            if (this.eventTypes.indexOf(eventType) < 0) {
                throw new Error('Event type \'' + eventType + '\' not recognized');
            }
            if (eventMap && eventMap[eventType]) {
                listeners = eventMap[eventType];
                for (i = 0, len = listeners.length; i < len; i++) {
                    listeners[i].func.apply(listeners[i].context || null, args);
                }
            }
        }
    };
    var P = function () {};
    return function (objOrFunc, eventTypes) {
        var obj = objOrFunc, x, i;
        //If function then add features to prototype. This will save user from explicitly adding events to every instance of the class.
        if (typeof objOrFunc === 'function') {
            obj = objOrFunc.prototype;
            //Copy event types from ancestor chain
            P.prototype = obj;
            eventTypes = eventTypes.concat((new P()).eventTypes || []);
            eventTypes.sort();
            //Remove duplicates
            for (i = 1; i < eventTypes.length; i++) {
                if (eventTypes[i] === eventTypes[i - 1]) {
                    eventTypes.splice(i--, 1);
                }
            }
        }
        obj.eventTypes = (obj.eventTypes || []).concat(eventTypes);
        for (x in methods) {
            if (methods.hasOwnProperty(x)) {
                obj[x] = methods[x];
            }
        }
    };
}());

var restaurant = {};
makePublisher(restaurant, ['freefood']);

var me = {
    onFreeFood: function (menu) {
        if (menu.indexOf('spicy indian dishes') > -1) {
            console.log('Rush to restaurant before everything gets over');
        }
    }
};
//Register a listener
restaurant.on('freefood', me.onFreeFood, me);

//Somewhere else in code
restaurant.fire('freefood', ['spicy indian dishes']);
Few more tips: i. There are times you need a publisher class listening to an event, to chain the event to it's own subscribers. Hence in some implementation you'd see a relay method:
relay: function (publisher, eventType) {
    publisher.on(function () {
        this.fire(eventType, Array.prototype.slice.call(arguments, 0));
    }, eventType, this);
}
ii. Most of the time the communication between publisher and subscriber happens in one direction.
However sometimes you need to loosely couple two components but each needs to listen to different events on each other.
To achieve this you need a middle-man who has reference to both the objects, to wire-up each others listeners.

5. Decorator

Progressively add functionality to an instance.
This pattern comes in handy in those cases where creating classes for each behavior you want to add to an instance might be an overkill.

function tea() {}
tea.prototype.getPrice = function () {
    return 1;
};

tea.addMilk = function (obj) {
    var getPrice = obj.getPrice;
    obj.getPrice = function () {
        return getPrice() + 1;
    };
    return obj;
};
tea.addSugar = function (obj) {
    var getPrice = obj.getPrice;
    obj.getPrice = function () {
        return getPrice() + 0.5;
    };
    return obj;
};

var mytea = new tea();
mytea = tea.addMilk(mytea);
mytea = tea.addSugar(mytea);
console.log(mytea.getPrice());
This pattern is helpful in UI creation: E.g: panel = resizable(scrollable(panel));

6. Augmenting

Adding functionality to prototype of a function. The difference between inheritance and augmenting would be that when you "augment", properties are added to an existing "class" (without creating a new class).
There are various ways to achieve this pattern. (You could mix it with the decorator pattern too). Note: This pattern could be an alternative to 'multiple inheritance' (which cannot be directly achieved with JS).
(Also note: I used this pattern on the publisher-subscriber implementation above. However below I implement it in a different way to make it distinct from non-augmenting functions)

function applyPadding() {
    this.padding = 0;
    this.getDimension = (function (self) {
        var getDimension = self.getDimension;
        return function () {
            var dim = getDimension();
            return {
                x: dim.x + 2 * this.padding,
                y: dim.y + 2 * this.padding,
            };
        };
    }(this));
    return this;
}

Frame = function () {...}
Frame.prototype = {...};
applyPadding.call(Frame.prototype);

blog comments powered by Disqus