# 命名空间
**关于术语的说明:**需要注意的是,在 TypeScript 1.5 中,命名法发生了变化。"Internal modules" 现在是 "namespaces"。"External modules" 现在只是 "modules",以符合
ECMAScript 2015
的术语,(即module X {
等同于现在首选的namespace X {
)。
这篇文章概述了在 TypeScript 中使用命名空间(以前是 "internal modules")来组织代码的各种方法。正如我们在关于术语的注释中提到的那样,"internal modules" 现在称为 "namespaces"。此外,在声明内部模块时使用 module
关键字的任何地方,都可以并且应该使用 namespace
关键字。这通过使用类似名称的术语使新用户重载来避免混淆新用户。
# 第一步
让我们从将在整个页面中用作示例的程序开始。我们编写了一组简单的字符串验证器,您可能会编写这些验证器来检查用户在网页表单上的输入或检查外部提供的数据文件的格式。
# 单个文件中的验证器
interface StringValidator {
isAcceptable(s: string): boolean;
}
let lettersRegexp = /^[A-Za-z]+$/;
let numberRegexp = /^[0-9]+$/;
class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
// Some samples to try
let strings = ["Hello", "98052", "101"];
// Validators to use
let validators: { [s: string]: StringValidator } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator();
// Show whether each string passed each validator
for (let s of strings) {
for (let name in validators) {
let isMatch = validators[name].isAcceptable(s);
console.log(`'${s}' ${isMatch ? "matches" : "does not match"} '${name}'.`);
}
}
# 命名空间
随着我们添加更多验证器,我们将需要某种组织方案,以便我们可以跟踪我们的类型,而不必担心与其他对象的名称冲突。与其将许多不同的名称放入全局命名空间,不如将我们的对象包装到一个命名空间中。
在本例中,我们会将所有与验证器相关的实体移动到名为 Validation
的命名空间中。因为我们希望这里的接口和类在命名空间之外可见,所以我们在它们前面加上 export
。相反,变量 lettersRegexp
和 numberRegexp
是实现细节,因此它们未导出,并且对命名空间外的代码不可见。在文件底部的测试代码中,我们现在需要在命名空间外使用时限定类型的名称,例如Validation.LettersOnlyValidator
。
# 命名空间验证器
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
const lettersRegexp = /^[A-Za-z]+$/;
const numberRegexp = /^[0-9]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
}
// Some samples to try
let strings = ["Hello", "98052", "101"];
// Validators to use
let validators: { [s: string]: Validation.StringValidator } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// Show whether each string passed each validator
for (let s of strings) {
for (let name in validators) {
console.log(
`"${s}" - ${
validators[name].isAcceptable(s) ? "matches" : "does not match"
} ${name}`
);
}
}
# 跨文件拆分
随着我们的应用程序的增长,我们希望将代码拆分到多个文件中以使其更易于维护。
# 多文件命名空间
在这里,我们将 Validation
命名空间拆分为多个文件。即使这些文件是分开的,它们也可以贡献给同一个命名空间,并且可以像在一个地方定义它们一样被使用。因为文件之间存在依赖关系,所以我们将添加引用标签来告诉编译器文件之间的关系。我们的测试代码没有改变。
# Validation.ts
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
}
# LettersOnlyValidator.ts
/// <reference path="Validation.ts" />
namespace Validation {
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
}
# ZipCodeValidator.ts
/// <reference path="Validation.ts" />
namespace Validation {
const numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
}
# Test.ts
/// <reference path="Validation.ts" />
/// <reference path="LettersOnlyValidator.ts" />
/// <reference path="ZipCodeValidator.ts" />
// Some samples to try
let strings = ["Hello", "98052", "101"];
// Validators to use
let validators: { [s: string]: Validation.StringValidator } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// Show whether each string passed each validator
for (let s of strings) {
for (let name in validators) {
console.log(
`"${s}" - ${
validators[name].isAcceptable(s) ? "matches" : "does not match"
} ${name}`
);
}
}
一旦涉及多个文件,我们需要确保所有编译的代码都被加载。有两种方法可以做到这一点。
首先,我们可以使用 outFile
选项使用串联输出将所有输入文件编译为单个 JavaScript 输出文件:
tsc --outFile sample.js Test.ts
编译器将根据文件中存在的引用标签自动对输出文件进行排序。您还可以单独指定每个文件:
tsc --outFile sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts
或者,我们可以使用按文件编译(默认)为每个输入文件生成一个 JavaScript 文件。如果生成了多个 JS 文件,我们需要在网页上使用 <script>
标签以适当的顺序加载每个发出的文件,例如:
# MyTestPage.html(摘录)
<script src="Validation.js" type="text/javascript" />
<script src="LettersOnlyValidator.js" type="text/javascript" />
<script src="ZipCodeValidator.js" type="text/javascript" />
<script src="Test.js" type="text/javascript" />
# 别名
另一种简化命名空间的方法是使用 import q = x.y.z
为常用对象创建较短的名称。不要与用于加载模块的 import x = require("name")
语法混淆,此语法只是为指定符号创建别名。您可以将这些类型的导入(通常称为别名)用于任何类型的标识符,包括从模块导入创建的对象。
namespace Shapes {
export namespace Polygons {
export class Triangle {}
export class Square {}
}
}
import polygons = Shapes.Polygons;
let sq = new polygons.Square(); // Same as 'new Shapes.Polygons.Square()'
请注意,我们不使用 require
关键字;相反,我们直接从我们正在导入的符号的限定名称中分配。这类似于使用 var
,但也适用于导入符号的类型和命名空间含义。重要的是,对于值,import
是与原始符号不同的引用,因此对别名 var
的更改不会反映在原始变量中。
# 使用其他 JavaScript 库
为了描述不是用 TypeScript 编写的库的形状,我们需要声明库公开的 API。因为大多数 JavaScript 库只公开少数顶级对象,所以命名空间是表示它们的好方法。
我们将未定义实现的声明称为 "ambient"。通常这些在 .d.ts
文件中定义。如果您熟悉 C/C++,您可以将这些视为 .h
文件。让我们看几个例子。
# 环境命名空间
流行的库 D3 在一个名为 d3
的全局对象中定义了它的功能。因为这个库是通过 <script>
标记(而不是模块加载器)加载的,所以它的声明使用命名空间来定义它的形状。为了让 TypeScript 编译器看到这个形状,我们使用环境命名空间声明。例如,我们可以如下开始编写它:
# D3.d.ts(简化摘录)
declare namespace D3 {
export interface Selectors {
select: {
(selector: string): Selection;
(element: EventTarget): Selection;
};
}
export interface Event {
x: number;
y: number;
}
export interface Base extends Selectors {
event: Event;
}
}
declare var d3: D3.Base;