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.