Namespaces

In previous examples, the classes created were in the global namespace. In a large project with dozens of classes, this quickly becomes unmanageable. It is a good practice to avoid global variables in JavaScript, just as it would be in almost any other programming language. To avoid polluting the global namespace, we will add support for hierarchical namespaces to our framework. Fortunately, JavaScript easily supports a namespace strategy similar to the use of packages in Java.

First we will define a class called XNamespace. While we could use any object to support namespaces, it is convenient to be able to identify if an object is a namespace using the instanceof operator. It is also a Good Idea™ to adhere to the Separation Of Concerns principle and keep all operations related to namespaces in a separate class. Here is the code for XNamespace.

01: function XNamespace(name, parent) { 02: this._name = name; 03: this._parent = parent; 04: }
05: XNamespace.prototype = { 06: constructor: XNamespace,
07: fullname: function() { 08: var parent = this._parent; 09: return (parent && parent instanceof XNamespace ? 10: (parent.fullname() + ".") : "") + this._name; 11: }
12: namespace: function(name) {
13: return this.traverse(name, function(name, part, current) {
14: if ( !current[part] ) { 15: current[part] = new XNamespace(part, current); 16: } 17: else if ( !(current[part] instanceof XNamespace) ) { 18: throw part + " is not a valid namespace."; 19: } 20: return current[part]; 21: }); 22: },
23: resolve: function(name) {
24: return this.traverse(name, function(name, part, current) {
25: var ns = current[part]; 26: return ns ? ns : null; 27: });
28: }, 29: traverse: function(name, tf) {
30: var parts = name instanceof Array ? name : name.split('.');
31: var current = window;
32: for ( i = 0; current && i < parts.length; ++i ) { 33: current = tf(name, parts[i], current); 34: }
35: return current;
36: } 37: }

The namespace and resolve methods demonstrate how functions are objects in JavaScript and can be passed as an object to other functions. Both methods need to iterate over the parts of a name and traverse a namespace hierarchy looking for a match. The code to traverse namespaces has been refactored into a traverse method. This method iterates over the elements of a name and calls a function for each element passing it the full name, the element, and the current namespace. The namespace method calls traverse passing it a function that creates elements of a namespace if they do not already exist. The resolve method calls traverse passing it a function that returns null if the element cannot be resolved to an object in the current namespace.

Next we will modify the framework to use namespaces.