JavaScript Patterns (v2)

Few years ago, I wrote a list of programming patterns that are useful when writing JS. I picked those from JavaScript Patterns book (by Stoyan Stefanov), from my day-to-day work and from my work on open-source library Lithium.

This article is a revision of the old article, with less implementation details and usage of Lithium (where applicable) to quickly bring the patterns into use. That way, the article would be more useful for new devs to bring them into practice.

1. Classes and inheritance

Previously I dedicated an entire article to this topic which goes deep into the gory details of "emulating" classes in JS. Here I'll give you the shortcut with Lithium.

var Tea = Li.extend(Object, {
    constructor: function (config) {
        this.type = config.type;
    },
    getPrice: function () {
        return 1;
    }
});

var MilkTea = Li.extend(Tea, {
    // This overrides base class's getPrice() method
    getPrice: function () {
        return this.super(arguments) + 0.5;
    }
});

var tea1 = new MilkTea({type: 'super strong'});
console.log(tea1.getPrice());
You can see how I used the special this.super() function to add the price from the base class's getPrice() method.

2. Module pattern

On a browser, JS variables would be accessible globally if they are not within a function (that same is not true on node.js). Module pattern can prevent global variables and functions.

(function() {
    var randomHex = function (c) {
        return ((Math.random() * 16)|0).toString(16);
    };
    window.randomColor = function () {
        return "#000000".replace(/0/g, randomHex);
    };
}());
Note that this pattern can be used to implement private/helper functions for methods of classes.
var ColorUtil = Li.extend(Object, {
    randomColor: (function () {
        var randomHex = function (c) {
            return ((Math.random() * 16)|0).toString(16);
        };
        return function () {
            return "#000000".replace(/0/g, randomHex);
        };
    }())
});

3. Argument and context binding

In JS, the context of a function ('this' keyword) is unlike most other programming languages. The context ('this') can be changed during a call to the 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.
To get the code working, we need to set (bind) the 'this' keyword to the myclass instance.
element.onclick = this.onclick.bind(this);
The previous code would now work as expected.

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.
Previously I mentioned that this pattern is essential for UI development. However, I rather now say that it is useful if and when you have the need for communicating between many components.

Here is an example of setting 'Last saved time' status using Lithium's Publisher class:

window.App = Li.extend(Li.Publisher, {
    constructor: function () {
        this.addEvents('saved');
        this.super(arguments);

        document.addEventListener('keypress', function (e) {
            //Save on Ctrl-S
            if (e.ctrlKey && e.keyCode === 83) {
                this.save();
            }
        }.bind(this));
    },
    save: function () {
        $.ajax({
            ...
            ...
            success: function () {
                ...
                this.trigger('saved');
            }
        });
    }
});

var Form = Li.extend(Object, {
    constructor: function (config) {
        this.savedStatusDiv = jQuery.parseHTML('<div></div>')[0];
        config.app.on('saved', function () {
            var d = new Date();
            this.savedStatusDiv.innerHTML = 'Last saved at ' + d.getHours() + ':' + d.getSeconds();
        }, this);
    },
    render: function () {
        document.appendChild(this.savedStatusDiv);
    }
});

var app = new App();
var form = newForm({app: app});
form.render();
Here App class extends Li.Publisher (to make it a Publisher), and pre-defines 'saved' as an event it can fire/trigger (this.addEvents('saved')). You can see that the form is listening/subscribed to the app object's 'saved' event when I do a config.app.on('saved', ...) call.

After save completes, I fire/trigger the 'saved' event, which causes the form's listener to execute and update the last saved time div.

Generally I make the root application object a publisher, so that events are "global" (makes things simpler that way).
However in addition to that, when the application gets larger and events gets more & more specialized, making smaller parts/components (of the application) into publishers (and triggering events on them) may make more sense.

5. Augmenting

'Augmenting' means 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).
I've used this trick to split large classes into multiple files.

Li.augment(App, {
    doSomething: function () {
    },

    moreMethod: function () {
    }
});
In one case where I was sharing client-side model with the server, I've used augment to add client specific methods to the model.

6. Decorator

Progressively add functionality to an instance.
Again, while I was sharing client-side model with the server, if I wanted to add client functionality to an existing method, then I used this pattern.

var origMethod = App.prototype.doSomething;
Li.augment(App, {
    doSomething: function () {
        var obj = origMethod.apply(this, arguments);
        obj.blah = 10;
        return obj;
    }
});

Written on Jan 19, 2016
blog comments powered by Disqus