1.10 Namespaces
Classes and interfaces support large-scale JavaScript development by providing a mechanism for describing how to use a software component that can be separated from that component’s implementation. TypeScript enforces encapsulation of implementation in classes at design time (by restricting use of private and protected members), but cannot enforce encapsulation at runtime because all object properties are accessible at runtime. Future versions of JavaScript may provide private names which would enable runtime enforcement of private and protected members.
In JavaScript, a very common way to enforce encapsulation at runtime is to use the module pattern: encapsulate private fields and methods using closure variables. The module pattern is a natural way to provide organizational structure and dynamic loading options by drawing a boundary around a software component. The module pattern can also provide the ability to introduce namespaces, avoiding use of the global namespace for most software components.
The following example illustrates the JavaScript module pattern.
(function(exports) {
var key = generateSecretKey();
function sendMessage(message) {
sendSecureMessage(message, key);
}
exports.sendMessage = sendMessage;
})(MessageModule);
This example illustrates the two essential elements of the module pattern: a module closure and a module object. The module closure is a function that encapsulates the module’s implementation, in this case the variable ‘key’ and the function ‘sendMessage’. The module object contains the exported variables and functions of the module. Simple modules may create and return the module object. The module above takes the module object as a parameter, ‘exports’, and adds the ‘sendMessage’ property to the module object. This augmentation approach simplifies dynamic loading of modules and also supports separation of module code into multiple files.
The example assumes that an outer lexical scope defines the functions ‘generateSecretKey’ and ‘sendSecureMessage’; it also assumes that the outer scope has assigned the module object to the variable ‘MessageModule’.
TypeScript namespaces provide a mechanism for succinctly expressing the module pattern. In TypeScript, programmers can combine the module pattern with the class pattern by nesting namespaces and classes within an outer namespace.
The following example shows the definition and use of a simple namespace.
namespace M {
var s = "hello";
export function f() {
return s;
}
}
M.f();
M.s; // Error, s is not exported
In this example, variable ‘s’ is a private feature of the namespace, but function ‘f’ is exported from the namespace and accessible to code outside of the namespace. If we were to describe the effect of namespace ‘M’ in terms of interfaces and variables, we would write
interface M {
f(): string;
}
var M: M;
The interface ‘M’ summarizes the externally visible behavior of namespace ‘M’. In this example, we can use the same name for the interface as for the initialized variable because in TypeScript type names and variable names do not conflict: each lexical scope contains a variable declaration space and type declaration space (see section 2.3 for more details).
The TypeScript compiler emits the following JavaScript code for the namespace:
var M;
(function(M) {
var s = "hello";
function f() {
return s;
}
M.f = f;
})(M || (M = {}));
In this case, the compiler assumes that the namespace object resides in global variable ‘M’, which may or may not have been initialized to the desired namespace object.