OOP in Javascript, the right way

In the past months I’ve covered a lot of topics involving the basics of javascript, several design patterns, and the power of RequireJS as a tool for better JS applications. Now it’s time to put the pieces together and let’s see what we can do with it all.

In this post I’m going to demonstrate how the caller extention pattern, can be used within a RequireJS application to create a method of object oriented programming that fits right in with many best practices known to classical OOP languages. To demonstrate we’ll use (a simplified version of) EventObject. A module designed to give event attaching and firing capabilities to any object or constructor.

// EventObject.js
define(function () {
    // EventObject simplified constructor
    function EventObject() {
        var protect = {},
            events  = {};

        // attach events by calling on('myEvent', callback)
        this.on = function (eventName, callback) {
            if (!events.hasOwnProperty(eventName)) {
                events[eventName] = [];
            }
            events[eventName].push(callback);
        };

        // fire an event
        protect.fire = function (eventName) {
            var i, event,
                fireEvents = events[eventName];

            for (i=0; i < fireEvents.length; i++) {
                fireEvents[i]();
            }
        }
        // Return protected variables
        if (this instanceof EventObject === false) {
            return protect;
        }
    }

    return EventObject;
});

Some advantages become apparent immediately as we look at this code:

  • There is only one constructor in this file. Making this code easier to manage and version.
  • There is only 1 public property, used for attaching events.
  • Firing events can only be done from the protect object.
  • Nothing is assigned to the global.
  • This constructor could be part of a library, and you wouldn't even know it.

Extending EventObject

Now let's imagine we want to create some sort of widget, that pulls in data from Twitter. And every time there is a new post, I want other parts of my page to know about it. I could do this by creating an object to which others could register. Something like this:

// twitget.js
define(['EventObject'], function (EventObject) {
    var twitget = {},
        protect = EventObject.call(twitget);

    // create twitget

    twitget.newTweet = function () {
        // render the tweet to the page

        // Notify the other widgets registered for the 'new tweet' event
        protect.fire('new tweet');
    }

    return twitget;
});

Because we only want one widget, we're not using a constructor for it. But we can still get access to the protect variable by using the call property on EventObject. We once again have a file with a very distinct purpose. Again this is very useful for keeping our project manageable. The dependency on EventObject is very clear and distinct. And we are still not putting anything in the global. So now let's put our twitget on the page.

// index.html
require(['twitget', 'tweetMonster'],
        function (twitget, tweetMonster)
{
    // register a callback to our event
    twitget.on('new tweet', tweetMonster.eatTweet);

    // Tell twitget where to put it's tweets
    twitget.whereItsAt('#twitget');

    // fire away!
    twitget.newTweet();
});

And that's it! Still nothing in the global. Again a script with a very clear purpose, and we've managed to keep our code simple and easy to read. Building on this idea of modules requiring other modules gets us away from the ever growing monster scripts, and into the realm of scalable application architecture.

Before I learned to work with RequireJS and discovered how to take full advantage of the things javascript has to offer, I've build quite a few of those monsters myself. And no matter how well they are documented, no matter how well they are designed. Eventually scripts will simply be too big and we'll have to start moving things into separate files. And once we start doing that we'll need a way to deal with dependencies. And we'll need a way to do proper inheritance, preferably without the interference of complicated libraries that go against the grain of the javascript language. This is a solution that certainly seems to fit the requirements. Which makes me think we're on to something here.

This entry was posted in Javascript and tagged , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>