Caveats of the JS Module Pattern

Javascript modules are a nifty construct which I was lucky enough to learn firsthand from Douglas Crockford in his (then) small classes at Yahoo c2005. He didn’t use the term “module pattern” then — I expect he hadn’t thought of the phrase yet. It was just a way to have private variables and methods.

I still love them, but I have some caveats with regards to using them for non-singleton objects.

First, what is a module?

var counter = function(){
    var my = {
        count: 0,
        max: 5,
        isPastMax: function(){
            return this.count > this.max;
        }
    };
    //This is what gets assigned to the variable "counter"
    return {
        getCount: function(){
            return my.count;
        },
        reset: function(){
            my.count = 0;
        },
        increment: function(){
            my.count++;
            if (my.isPastMax()) {
                this.reset();
            }
        }
    };
}(); //NOTE the (). That makes the outer function run and return immediately

A module is nothing more than a function which returns a public API object which, through the magic of closure, has access to truly private methods and variables. Here I’ve put them all as properties of private variable my.

counter is now a singleton object that maintains a counter state privately. No more methods can be added to it which reach anything in my that we haven’t explicitly allowed.

This is all well and good. But what if we want more than one counter? Let’s take off the trailing () so that we have a factory that produces counters:

var Counter = function(){
   //just as above
}; //NOTE the absence of ()!
var instances = [];
for (var i=0; i<100; i++) {
    instances.push(Counter());
}

Now we have 100 instances of the Counter module!

Unfortunately, the encapsulation has come with a price in this case. Every function we defined within Counter has to be created again and occupy more memory!

Sometimes this is acceptable, if the number to be created is low and the module is small. If you’ve got a module that goes on for a few pages and you intend to make a few dozen instances, expect to be eating up substantially more memory than a similar feature using a non-encapsulated object, like so:

var Counter = {
    _count: 0,
    _max: 5,
    _isPastMax: function(){
        return this._count > this._max;
    },
    reset: function(){
        this._count = 0;
    },
    increment: function(){
        this._count++;
    },
    getCount: function(){
        return this._count;
    }
};
Object.extend = Object.extend || function(a, b){
    for (var k in b) {
        if (b.hasOwnProperty(k)) { a[k] = b[k]; }
    }
    return a;
};
var instances = [];
for (var i=0; i<100; i++) {
    instances.push(
        Object.extend({}, Counter)
    );
}

All of the functions exist only once in memory, referenced by the individual instance objects.

Make no doubt: the module pattern is a tool that every good Javascript developer should understand. It provides excellent encapsulation that can’t be externally broken. It’s not without its drawbacks, however.