Recursive initialization of JavaScript objects

It’s been a frequent practice of mine lately to write my Javascript as “object oriented” as is realistically possible. Doing so has made my code incredibly easier for me to maintain, document, and read. The more I write this way, the more non-OO Javascript stands out to me as the less desirable alternative.

The purpose of this post is not to explain how or why to write Javascript objects or even best practices. (A good, quick article to read first can be found on Konr Ness‘s post How to Create Javascript Objects.) Rather, I wanted to introduce the idea of recursive initialization of objects and their “children”, if you will.

One common practice for me (that I borrowed from others), is to include an init() method on the Javascript objects that I write, provided such a method would actually have any usefulness. This allows me to set the state of the object so that it can behave as intended. Additionally, with the advent of jQuery—and with the general nature of Javascript’s usefulness in DOM manipulation—I regularly find myself including a bind() method, which would include all of the event bindings I might need for that object.

That being said, I have started making some of those objects I write smaller, and simply writing more of them, so as to compartmentalize functionality into related groups. This tends to leave me writing multiple init() calls, like so:

objectOne.init();
objectTwo.init();
objectThree.init();
objectThree.subObjectOne.init();
objectThree.subObjectThree.init();

The explicit need to initialize each object (or even include the subsequent object initialization calls in the init() method of the first object that is initialized) tends to feel like a waste of code to me, but I can’t deny that I really need to be in control of when an object (or each object) is initialized.

That being said, I found myself writing a fake class function that returns an object with a default set of methods, public and private. I knew that the “InitObject”, as I refer to it, would need the ability to cascade initialization to its “child” objects (e.g. parentObject.childObject). Additionally, I would need to be able to (optionally) set the order of initialization, in case one child object needed to be initialized before another. With this challenge in mind, I threw together the “class” below.

InitObject = function(object) {

    /**
     * Stack of strings that represent child objects to initialize first
     *
     * @var array
     */
    var initStack = [];

    /**
     * Verifies that the given object can be initialized
     *
     * @param subject mixed Subject to be tested
     *
     * @return boolean
     */
    var hasInitFunction = function(subject)
    {
        return subject
            && typeof(subject) === 'object'
            && typeof(subject.init) === 'function';

    };

    /**
     * Base init object (before extending)
     *
     * @var object
     */
    var base = {

        /**
         * Whether this instance has been initialized yet
         *
         * @var boolean
         */
        initialized: false,

        /**
         * Starts the initialization process.  If not overwritten, initializes
         * own instance if it has not already been initialized, and passes
         * initialization to child objects regardless
         *
         * @return void
         */
        init: function()
        {
            if (!this.initialized) {
                // Mark this instance as intialized
                this.initialized = true;
                this.initSelf();
            }
            this.initChildren();
        },

        /**
         * Resets initialized to false
         *
         * @return void
         */
        resetStatus: function()
        {
            this.initialized = false;
        },

        /**
         * Contains any initialization for the instance and marks itself as
         * initialized.  Runs bind() function, if it exists.
         *
         * @return void
         */
        initSelf: function()
        {
           // run this.bind() if it exists and is a function
            if (typeof(this.bind) === 'function') {
                this.bind();
            }
        },

        /**
         * Runs through the initialization stack, then all children and begins
         * initialization on each, in that order.
         *
         * @return void
         */
        initChildren: function()
        {
            // Initialize from the stack first
            if (initStack.length) {
                for (i = 0; i < initStack.length; i++) {
                    if (this.hasValidChildObject(initStack[i])) {
                        this[initStack[i]].init();
                    }
                }
            }
            // Initialize everything else
            for (var j in this) {
                if (this.hasOwnProperty(j) && hasInitFunction(this[j])) {
                    this[j].init();
                }
            }
        },

        /**
         * Verifies that the string from the stack represents an actual child
         * object
         *
         * @param string string Property name to test.
         */
        hasValidChildObject: function(string)
        {
            return this.hasOwnProperty(string)
                && hasInitFunction(this[string])
        },

        /**
         * Adds an item to the stack, at the beginning or the end
         *
         * @param string     string  String to add to the stack
         * @param prioritize boolean
         *
         * @return array|boolean
         */
        addToStack: function(string, prioritize)
        {
            if (typeof(string) === "string" && hasInitFunction(this[string])) {
                if (prioritize) {
                    initStack.unshift(string);
                    return initStack;
                }
                initStack.push(string);
                return initStack;
            }
            return false;
        }
    };

    if (typeof(object) === 'object') {
        return jQuery.extend(base, object);
    }

    return base;

};

The InitObject is setup with methods init(), initSelf(), and initChildren(), each of which can be overwritten (although I wouldn’t recommend overwriting initChildren(), as it could break the cascade). The initSelf() method, by default, also checks for a bind() method and runs it, if it exists.

Implementation is fairly simple, as the InitObject function takes in an object, and extends the base object with the object provided to the function.

Consider the following implementation:

jQuery(document).ready(function() {
    jaSpr.addToStack('design');
    jaSpr.init();
});


/**
 * Parent object
 */
var jaSpr = new InitObject();


/**
 * Animations parent object
 */
jaSpr.animations = new InitObject();


/**
 * Logo animations
 */
jaSpr.animations.logo = new InitObject({

    /**
     * Custom self initialization.
     * Delays thisObj.bind until after initial animation (see hideLongParts())
     *
     * @return void
     */
    initSelf: function()
    {
        var thisObj = this;

        this.initialized = true;

        jQuery('.paren, .logo .last').each(function() {
            jQuery(this).data('js-width', jQuery(this).width());
        });


        this.hideLongParts(function() {
            thisObj.bind();
        });
    },

    /**
     * Custom bindings (NOT called by default initSelf() method)
     *
     * @return void
     */
    bind: function()
    {
        var thisObj = this;
        jQuery('.logo').hover(function(){
            thisObj.mouseEnter(jQuery(this));
        },function(){
            thisObj.mouseExit(jQuery(this));
        });
    },

    ...

One drawback is that I’ve made use of jQuery’s $.extend() function, which means jQuery has to be included to run this object, but I just didn’t feel like doing it without, as I was just trying to make something work for my site, and I already had jQuery included.

At any rate, please feel free to let me know your thoughts.

2 thoughts on “Recursive initialization of JavaScript objects”

Leave a Reply