Cover by Addy Osmani

Safari, the world’s most comprehensive technology and business learning platform.

Find the exact information you need to solve a problem on the fly, or go deeper to master the technologies and skills you need to succeed

Start Free Trial

No credit card required

O'Reilly logo

Advanced Namespacing Patterns

We’ll now explore some advanced patterns and utilities that I have found invaluable when working on larger applications, some of which have required a rethink of traditional approaches to application namespacing. I’ll note that I am not advocating any of the following as the way to namespace, but rather ways that I have found work in practice.

Automating Nested Namespacing

As we’ve reviewed, nested namespaces can provide an organized hierarchy of structure for a unit of code. An example of such a namespace could be the following: application.utilities.drawing.canvas.2d. This can also be expanded using the object literal pattern to be:

var application = {
      utilities:{
          drawing:{
              canvas:{
                  2d:{
                          //...
                  }
              }
          }
    }        
};

One of the obvious challenges with this pattern is that each additional layer we wish to create requires yet another object to be defined as a child of some parent in our top-level namespace. This can become particularly laborious when multiple depths are required as our application increases in complexity.

How can this problem be better solved? In JavaScript Patterns, Stoyan Stefanov presents a very clever approach for automatically defining nested namespaces under an existing global variable. He suggests a convenience method that takes a single string argument for a nest, parses this, and automatically populates our base namespace with the objects required.

The method he suggests using is the following, which I’ve updated it to be a generic function for easier reuse with multiple namespaces:

// top-level namespace being assigned an object literal
var myApp = myApp || {};

// a convenience function for parsing string namespaces and 
// automatically generating nested namespaces
function extend( ns, ns_string ) {
    var parts = ns_string.split("."),
        parent = ns,
        pl;

    pl = parts.length;

    for ( var i = 0; i < pl; i++ ) {
        // create a property if it doesn't exist
        if ( typeof parent[parts[i]] === "undefined" ) {
            parent[parts[i]] = {};
        }

        parent = parent[parts[i]];
    }

    return parent;
}

// Usage:
// extend myApp with a deeply nested namespace
var mod = extend(myApp, "myApp.modules.module2");

// the correct object with nested depths is output
console.log(mod);

// minor test to check the instance of mod can also
// be used outside of the myApp namesapce as a clone
// that includes the extensions 

// Outputs: true
console.log(mod == myApp.modules.module2); 

// further demonstration of easier nested namespace
// assignment using extend
extend(myApp, "moduleA.moduleB.moduleC.moduleD");
extend(myApp, "longer.version.looks.like.this");
console.log(myApp);

Figure 13-1 shows the Chrome Developer Tools output:

Chrome Developer Tools output

Figure 13-1. Chrome Developer Tools output

Where one would previously have had to explicitly declare the various nests for their namespace as objects, this can now be easily achieved using a single, cleaner line of code.

Dependency Declaration Pattern

We’re now going to explore a minor augmentation to the Nested Namespacing pattern which we’ll refer to as the Dependency Declaration pattern. We all know that local references to objects can decrease overall lookup times, but let’s apply this to namespacing to see how it might look in practice:

// common approach to accessing nested namespaces
myApp.utilities.math.fibonacci( 25 );
myApp.utilities.math.sin( 56 );
myApp.utilities.drawing.plot( 98,50,60 );

// with local/cached references
var utils = myApp.utilities,
maths = utils.math,
drawing = utils.drawing;

// easier to access the namespace
maths.fibonacci( 25 );
maths.sin( 56 );
drawing.plot( 98, 50,60 );

// note that the above is particularly performant when 
// compared to hundreds or thousands of calls to nested 
// namespaces vs. a local reference to the namespace

Working with a local variable here is almost always faster than working with a top-level global (e.g., myApp). It’s also both more convenient and more performant than accessing nested properties/subnamespaces on every subsequent line and can improve readability in more complex applications.

Stoyan recommends declaring localized namespaces required by a function or module at the top of our function scope (using the single-variable pattern) and calls this a Dependency Declaration pattern. One of the benefits this offers is a decrease in locating dependencies and resolving them, should we have an extendable architecture that dynamically loads modules into our namespace when required.

In my opinion, this pattern works best when working at a modular level, localizing a namespace to be used by a group of methods. Localizing namespaces on a per-function level, especially where there is significant overlap between namespace dependencies, would be something I would recommend avoiding where possible. Instead, define it further up and just have them all access the same reference.

Deep Object Extension

An alternative approach to automatic namespacing is deep object extension. Namespaces defined using object literal notation may be easily extended (or merged) with other objects (or namespaces) such that the properties and functions of both namespaces can be accessible under the same namespace post-merge.

This is something that’s been made fairly easy to accomplish with modern JavaScript frameworks (e.g., see jQuery’s $.extend); however, if looking to extend objects (namespaces) using vanilla JS, the following routine may be of assistance.

// extend.js
// Written by Andrew Dupont, optimized by Addy Osmani

function extend( destination, source ) {

    var toString = Object.prototype.toString,
        objTest = toString.call({});

    for ( var property in source ) {
        if ( source[property] && objTest === toString.call(source[property]) ) {
            destination[property] = destination[property] || {};
            extend(destination[property], source[property]);
        } else {
            destination[property] = source[property];
        }
    }
    return destination;

};

console.group( "objExtend namespacing tests" );

// define a top-level namespace for usage
var myNS = myNS || {};

// 1. extend namespace with a "utils" object
extend(myNS, { 
        utils:{
        }
});

console.log( "test 1" , myNS);
// myNS.utils now exists

// 2. extend with multiple depths (namespace.hello.world.wave)
extend(myNS, {
                hello:{
                        world:{
                                wave:{
                                    test: function(){
                                        //...
                                    }
                                }
                        }
                }
});

// test direct assignment works as expected
myNS.hello.test1 = "this is a test";
myNS.hello.world.test2 = "this is another test";
console.log( "test 2", myNS );

// 3. what if myNS already contains the namespace being added 
// (e.g. "library")? we want to ensure no namespaces are being 
// overwritten during extension

myNS.library = {
        foo:function () {}
};

extend( myNS, {
        library:{ 
                bar:function(){
                    //... 
                }
        }
});

// confirmed that extend is operating safely (as expected)
// myNS now also contains library.foo, library.bar
console.log( "test 3", myNS ); 


// 4. what if we wanted easier access to a specific namespace without having
// to type the whole namespace out each time?

var shorterAccess1 = myNS.hello.world;
shorterAccess1.test3 = "hello again";
console.log( "test 4", myNS);

//success, myApp.hello.world.test3 is now "hello again"

console.groupEnd();

Note

The above implementation is not cross-browser compatible for all objects and should be considered a proof-of-concept only. One may find the Underscore.js extend() method a simpler, more cross-browser-friendly implementation to start with: http://documentcloud.github.com/underscore/docs/underscore.html#section-67. Alternatively, a version of the jQuery $.extend() method extracted from core can be found here: https://github.com/addyosmani/jquery.parts.

For developers who are going to use jQuery in their applications, one can achieve the exact same object namespace extensibility with $.extend as follows:

// top-level namespace
var myApp = myApp || {};

// directly assign a nested namespace
myApp.library = {
  foo:function(){ 
    //...
  }
};

// deep extend/merge this namespace with another
// to make things interesting, let's say it's a namespace
// with the same name but with a different function
// signature: $.extend( deep, target, object1, object2 )
$.extend( true, myApp, {
    library:{ 
        bar:function(){
            //...
        }
    }
});

console.log("test", myApp); 
// myApp now contains both library.foo() and library.bar() methods
// nothing has been overwritten which is what we're hoping for.

For the sake of thoroughness, please see here for jQuery $.extend equivalents to the rest of the namespacing experiments found in this section.

Recommendation

Reviewing the namespace patterns we’ve explored in this section, the option that I would personally use for most larger applications is nested object namespacing with the Object Literal pattern. Where possible, I would implement this using automated nested namespacing, however, this is just a personal preference.

IIFEs and single global variables may work fine for applications in the small to medium range, however, larger code bases requiring both namespaces and deep subnamespaces require a succinct solution that promotes readability and scales. I feel this pattern achieves all of these objectives well.

I would also recommend trying out some of the suggested advanced utility methods for namespace extension, as they really can save time in the long run.

Find the exact information you need to solve a problem on the fly, or go deeper to master the technologies and skills you need to succeed

Start Free Trial

No credit card required