Taking Control of Your JavaScript Objects With the Revealing Module Pattern

Posted on November 06, 2015 | By Matt Stauffer

Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.

On the latest episode of the Laravel Podcast, I Love the Things You Are Saying, we were talking about VueJS, and Taylor mentioned that sometimes it's annoying to have long objects that you pass around in JavaScript frameworks. You know the ones: you're defining a series of parameters and methods and you have to cram them all into a comma separated list so they are set as properties on an object:

Vue.doSomethingOrOther({
    onething: function () {

    },
    otherThing: function () {

    },
    etcetera: 'etcetera'
});

On the podcast I mentioned my undying love for the Revealing Module Pattern and promised an example, so here goes.

Why do we Need The Revealing Module Pattern?

I first learned about the Revealing Module Pattern through Addy Osmani's book Learning JavaScript Design Patterns.

Let's take a quick example to show why Revealing Module is great. Let's presume we want an Analytics object. We want to be able to use it throughout our JavaScript to make calls to Google Analytics, but we want a simpler syntax.

var Analytics = {};

Let's give it two methods, pageView and action.

var Analytics = {
    pageView: function () {
        GoogleAnalytics.prepSomethingOrOther();
        GoogleAnalytics.pushOrSomething('pageView');
    },
    action: function (key) {
        GoogleAnalytics.prepSomethingOrOther();
        GoogleAnalytics.pushOrSomething('action', key);
    }
};

Well, take a look at that—it's a bit of repeated code! If only we could have private methods, we could extract some sort of `pushOrSomething' method:

var Analytics = {
    pushOrSomething: function () {
        GoogleAnalytics.prepSomethingOrOther();
        // Use function.apply to pass the called parameters along
        GoogleAnalytics.pushOrSomething.apply(this, arguments);
    },
    pageView: function () {
        this.pushOrSomething('pageView');
    },
    action: function (key) {
        this.pushOrSomething('action', key);
    }
};

This looks good, but our big problem here is that we've now exposed Analytics.pushOrSomething() to the public. Additionally, we haven't hit it quite yet, but when we start building this object out, we'll run into Taylor's pain point of the constantly-growing comma-separated list.

The Self-Executing Anonymous Function

The Revealing Module Pattern relies on a concept call the Self-Executing Anonymous Function. Let's take a look, first, at an anonymous function:

var AnalyticsGenerator = function() {
    return {};
};

var Analytics = AnalyticsGenerator();

Great, so we have an un-named function that returns something IF we run it. But that's an awkward syntax, especially if we only expect to run this once. If only we could call the function as soon as we define it...

var Analytics = (function() {
    return {};
})();

By George, we've done it! We just wrapped the function in parentheses, and then added a second set of parentheses afterwards to indicate it should be executed. Now Analytics is defined as the result of this function's execution, which in this case is just an empty object.

The Revealing Module Pattern

Finally. The pattern. Check out our previous example, but now Revealing-Moduled:

var Analytics = (function () {

    var _pushOrSomething = function () {
        GoogleAnalytics.prepSomethingOrOther();
        // Use function.apply to pass the called parameters along
        GoogleAnalytics.pushOrSomething.apply(this, arguments);
    };

    var pageView = function () {
        _pushOrSomething('pageView');
    };

    var action = function (key) {
        _pushOrSomething('action', key);
    };

    return {
        pageView: pageView,
        action: action
    };
})();

Notice that we've updated the calls within pageView and action to reference the function name without this, and we've prefaced the "private" function pushOrSomething with an underscore, just as a reminder to keep it private.

At the end, we've defined what we want to return, and anything that's not in that return object is "private" and can't be called by the public. This also works for properties, and you can even do all sorts of procedural work, if you want:

var Analytics = (function () {
    var variableOrWhatever = 42;
    variableOrWhatever *= 1.0;

    var _pushOrSomething = function () {
        GoogleAnalytics.initialize(variableOrWhatever);
        GoogleAnalytics.somethingOrOther();
        // Use function.apply to pass the called parameters along
        GoogleAnalytics.pushOrSomething.apply(this, arguments);
    };

    var pageView = function () {
        _pushOrSomething('pageView');
    };

    var action = function (key) {
        _pushOrSomething('action', key);
    };

    return {
        variableOrWhatever: variableOrWhatever,
        pageView: pageView,
        action: action
    };
})();

If you want to see this running, and see what happens when you try to call a "private" method, check out this JSBin. Or, just try the code above and see what happens when you run Analytics._pushOrSomething (hint: your browser won't like it).

The sky's the limit, kids. Now anywhere you need to generate a JavaScript object you have much more freedom to quickly create private methods and run procedural nasty prep code in the midst of creating it.

ES6 Addendum

A lot of the reason we need this sort of stuff is because JavaScript is based on Prototypes, not Classes & Objects. That's slowly been changing over time, and ES6 has made a huge difference in this aspect. So if you're writing ES6, you'll find less use for this pattern—but I'd still suggest keeping it in your toolbelt.


Comments? I'm @stauffermatt on Twitter


Tags: javascript  •  vuejs