Skip to content
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

命名空间

关于术语的说明: 需要注意的是,在 TypeScript 1.5 中,术语发生了变化。 “内部模块”现在称为“命名空间”。 “外部模块”现在简称为“模块”,以便与 ECMAScript 2015 的术语保持一致,(也就是说 module X { 等价于现在更推荐的 namespace X {)。

本文概述了在 TypeScript 中使用命名空间(以前称为“内部模块”)组织代码的各种方式。 正如我们在术语说明中提到的,“内部模块”现在称为“命名空间”。 此外,在声明内部模块时使用 module 关键字的任何地方,都可以并且应该使用 namespace 关键字来代替。 这样可以避免用相似命名的术语重载新用户,从而避免混淆。

第一步

让我们从将在本页中用作示例的程序开始。 我们编写了一组简单的字符串验证器,就像你可能用来检查网页表单中用户输入或检查外部提供的数据文件格式一样。

单个文件中的验证器

ts
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}'.`);
  }
}

命名空间

随着我们添加更多验证器,我们希望有一种组织方案,以便能够跟踪我们的类型,而不必担心与其他对象发生名称冲突。 与其将许多不同的名称放入全局命名空间,不如将我们的对象包装到一个命名空间中。

在此示例中,我们将所有与验证器相关的实体移入名为 Validation 的命名空间。 因为我们希望这里的接口和类在命名空间外部可见,所以我们在它们前面加上 export。 相反,变量 lettersRegexpnumberRegexp 是实现细节,因此它们保持未导出状态,并且对命名空间外部的代码不可见。 在文件底部的测试代码中,我们现在需要在命名空间外部使用时限定类型的名称,例如 Validation.LettersOnlyValidator

命名空间化的验证器

ts
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}`
    );
  }
}

拆分到多个文件

随着应用程序的增长,我们希望将代码拆分到多个文件中,以便于维护。

多文件命名空间

在这里,我们将把 Validation 命名空间拆分到多个文件中。 尽管文件是分开的,但它们各自可以为同一个命名空间做出贡献,并且可以像所有内容都定义在一个地方一样被使用。 由于文件之间存在依赖关系,我们将添加引用标签来告诉编译器文件之间的关系。 我们的测试代码在其他方面保持不变。

Validation.ts
ts
namespace Validation {
  export interface StringValidator {
    isAcceptable(s: string): boolean;
  }
}

LettersOnlyValidator.ts
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
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
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 输出文件:

Shell
tsc --outFile sample.js Test.ts

编译器将根据文件中存在的引用标签自动排序输出文件。你也可以单独指定每个文件:

Shell
tsc --outFile sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts

或者,我们可以使用每个文件单独编译(默认方式)为每个输入文件生成一个 JavaScript 文件。 如果生成了多个 JS 文件,我们需要在网页上使用 <script> 标签以正确的顺序加载每个生成的文件,例如:

MyTestPage.html (摘录)
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") 语法混淆,此语法只是为指定的符号创建别名。 你可以对任何类型的标识符使用这种导入(通常称为别名),包括从模块导入创建的对象。

ts
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,但也适用于导入符号的类型和命名空间含义。 重要的是,对于值来说,import 是不同于原始符号的引用,因此对别名的 var 的更改不会反映在原始变量中。

使用其他 JavaScript 库

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

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

环境命名空间

流行的库 D3 将其功能定义在一个名为 d3 的全局对象中。 由于该库是通过 <script> 标签(而不是模块加载器)加载的,其声明使用命名空间来定义其形状。 为了让 TypeScript 编译器看到这个形状,我们使用环境命名空间声明。 例如,我们可以开始编写如下:

D3.d.ts (简化摘录)
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;