十四、命名空间

关于术语的说明:需要注意的是,在TypeScript 1.5中,术语已经改变。”内部模块 “ 现在是 “命名空间”。”外部模块 “现在只是 “模块”,以便与 ECMAScript 2015的术语保持一致,(即 module X { 等同于现在的 namespace X {)。

这篇文章概述了在TypeScript中使用命名空间(以前的 “内部模块”),用各种方法来组织你的代码。正如我们在术语说明中所暗示的,”内部模块 “现在被称为 “命名空间”。此外,在声明内部模块时,凡是使用 module 关键字的地方,都可以而且应该使用 namespace 关键字来代替。这就避免了新用户因使用类似的术语而感到困惑。

14.1 第一步

让我们从本页中我们将使用的程序开始。作为例子,我们写了一小套简单的字符串验证器,用来检查用户在网页中的表单中的输入,或者检查外部提供的数据文件的格式。

14.2 单一文件中的验证器

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);
}
}
// 一些测试案例
let strings = ["Hello", "98052", "101"];
// 要使用的验证器
let validators: { [s: string]: StringValidator } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator();
// 显示每一个字符串是否通过了每个验证器
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}'.`);
}
}

14.2 命名方式

当我们添加更多的验证器时,我们会希望有某种组织方案,这样我们就可以跟踪我们的类型,而不用担心与其他对象的名称冲突。与其把很多不同的名字放到全局命名空间中,不如把我们的对象包装成一个命名空间。

在这个例子中,我们将把所有与验证器相关的实体移到一个叫做 Validation 的命名空间中。因为我们希望这里的接口和类在命名空间之外是可见的,所以我们在它们前面加上 export。相反,变量 lettersRegexpnumberRegexp 是实现细节,所以它们没有被导出,也不会被命名空间以外的代码看到。在文件底部的测试代码中,我们现在需要限定在名字空间之外使用的类型的名称,例如 Validation.LettersOnlyValidator

14.3 命名的验证器

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);
}
}
}
// 一些测试案例
let strings = ["Hello", "98052", "101"];
// 要使用的验证器
let validators: { [s: string]: Validation.StringValidator } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// 显示每一个字符串是否通过了每个验证器
for (let s of strings) {
for (let name in validators) {
console.log(
`"${s}" - ${
validators[name].isAcceptable(s) ? "matches" : "does not match"
} ${name}`,
);
}
}

14.4 跨文件分割

随着我们的应用程序的增长,我们将希望把代码分成多个文件,以使它更容易维护。

14.5 多文件命名空间

在这里,我们将把我们的 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" />
// 一些测试案例
let strings = ["Hello", "98052", "101"];
// 要使用的验证器
let validators: { [s: string]: Validation.StringValidator } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// 显示每一个字符串是否通过了每个验证器
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" />

14.6 别名

另一个可以简化命名空间工作的方法是使用 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(); // 与'new Shapes.Polygons.Square()'等价

注意,我们没有使用 require关键字;相反,我们直接从我们要导入的符号的限定名称中分配。这类似于使用var,但也适用于导入符号的类型和命名空间的含义。重要的是,对于数值来说,导入是一个不同于原始符号的引用,所以对别名 var 的改变不会反映在原始变量上。

14.7 与其他JavaScript库一起工作

为了描述不是用TypeScript编写的库的形状,我们需要声明库所暴露的API。因为大多数JavaScript库只暴露了几个顶级对象,命名空间是表示它们的一个好方法。

我们把不定义实现的声明称为 “环境”。通常,这些都是在 .d.ts 文件中定义的。如果你熟悉C/C++,你可以把它们看作是 .h 文件。让我们来看看几个例子:

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;

特别声明: 本文转自 古艺散人老师 ,如有需要可前往原文预览查看。