As soon as you develop a slightly more extensive application in which the logic is distributed across several JavaScript files, or if you include third-party libraries over whose variable and function names you have no control, you should think about how you group source code components (variables, functions, objects, etc.).
After all, it can quickly happen that name conflicts arise when everything is defined on a global level.
The figure below shows an example: The two JavaScript files script1.js and script2.js each define a variable with the same name (MAX) and a function with the same name (add()). So long as both scripts are used separately (script1.js from index1.html and script2.js from index2.html), there are no conflicts. But as soon as both scripts are used at the same time (index3.html), the order of loading then determines which value the variable MAX gets and which of the two functions is available under add().
In languages such as C# and Java, a grouping of components is possible using features such as packages and namespaces, so that within different packages/namespaces, components with the same name may occur without influencing each other. However, neither packages nor namespaces exist in JavaScript—but the option to emulate namespaces does. This technique is called namespace design pattern.
The idea is to combine related variables and the like in a separate object that exists only once within an application and serves as a simple container (see next figure). This way, you’ve at least brought some structure into your source code and avoid defining variables in the global scope. So long as such container objects have a globally unique name (MathUtilities and MoreMathUtilities in the example), this approach doesn’t lead to any naming conflicts.
The listing below shows the source code for the components shown in the above figure. The MathUtilities object forms the container or namespace for the MAX variable and the add() function; the MoreMathUtilities object in turn forms the namespace for the components of the same name, MAX and add(). The variables and functions are accessed then via the container object.
var MathUtilities = MathUtilities || {};
MathUtilities.MAX = 4711;
MathUtilities.add = function(x, y) {
return x + y;
};
var result = MathUtilities.add(2, 2);
console.log(result); // 4
console.log(MathUtilities.MAX); // 4711
MathUtilities.MAX = 2345;
console.log(MathUtilities.MAX); // 2345
15
Note: To keep the following listings as realistic as possible, we use the var keyword to create variables, rather than the let and const keywords. JavaScript runtime environments that support the newer keywords usually also have support for the native modules mentioned previously, so using let, for example, in combination with the following design patterns would be rather unusual—or in some cases wouldn’t work at all (see next note).
The call according to the var MathUtilities = MathUtilities || {}; pattern prevents a possibly already existing MathUtilities object from being overwritten.
In the more compact object-literal notation, the whole thing then looks as shown in this listing.
var MathUtilities = MathUtilities || {
MAX: 4711,
add: function(x, y) {
return x + y;
}
};
Note: The notation with the let keyword (i.e., let = MathUtilities || {}) doesn’t work in this case; it produces a ReferenceError.
Note: The namespace design pattern doesn’t prevent variables, functions, and so on of the corresponding container object from being reset (overwritten) from outside (see the first code listing of this post).
Now, of course, when assigning the name for a container object, you may choose a name for which there is already a variable of the same name (e.g., in an included JavaScript library). To get around this, in practice, as shown in the two listings below, namespaces can also be nested (nested namespacing), using the convention already known from Java or C# of deploying reverse-written domain names depending on the respective project.
var de = de || {};
de.philipackermann = de.philipackermann || {};
de.philipackermann.javascript = de.philipackermann.javascript || {};
var de = de || {
philipackermann : {
javascript : { }
}
};
Advantage of Domain Names with Namespaces: The advantage of using reverse-spelled domain names as the basis for a namespace structure is that domains are unique worldwide. This ensures that no other developer in the world uses the same namespace (assuming everyone adheres to this convention).
With the namespace design pattern, the problems of name conflicts are solved, but not, as you’ve seen, those of the visibility of variables and functions. However, true to the concept of information hiding (also known as data encapsulation), when modeling objects it makes sense to make only as much information available to the outside world as is absolutely necessary.
Editor’s note: This post has been adapted from a section of the book JavaScript: The Comprehensive Guide by Philip Ackermann.
Comments