[中文] https://www.tslang.cn/docs/handbook/namespaces.html
[英文] https://www.typescriptlang.org/docs/handbook/namespaces.html

对照着看疗效更好

基于接口的多种实现

下面提供了两段代码

  1. 第一段是定义了 StringValidator 接口,然后在两个 class 中实现了它,目的是使用 Validator 校验字符。
  2. 第二段是playground处理后的代码,可以看到类型描述相关的内容都不会被保留。 ```json 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 }'.); } }

  1. ```json
  2. let lettersRegexp = /^[A-Za-z]+$/;
  3. let numberRegexp = /^[0-9]+$/;
  4. class LettersOnlyValidator {
  5. isAcceptable(s) {
  6. return lettersRegexp.test(s);
  7. }
  8. }
  9. class ZipCodeValidator {
  10. isAcceptable(s) {
  11. return s.length === 5 && numberRegexp.test(s);
  12. }
  13. }
  14. // Some samples to try
  15. let strings = ["Hello", "98052", "101"];
  16. // Validators to use
  17. let validators = {};
  18. validators["ZIP code"] = new ZipCodeValidator();
  19. validators["Letters only"] = new LettersOnlyValidator();
  20. // Show whether each string passed each validator
  21. for (let s of strings) {
  22. for (let name in validators) {
  23. let isMatch = validators[name].isAcceptable(s);
  24. console.log(`'${s}' ${isMatch ? "matches" : "does not match"} '${name}'.`);
  25. }
  26. }

为什么要使用命名空间

上述代码中,我们增加了 interface、class、let变量。随着更多的Validator,会催生更多的class 和 let变量。为了(1)防止变量和其他对象的冲突(2)有个统一的地方对代码进行组织,我们定义了命名空间namespace,可以这么通俗地理解它:使用namespace定义了一个对象,所有相关的属性、方法都通过这个对象的名称才能访问到。

  1. namespace Validation {
  2. export interface StringValidator {
  3. isAcceptable(s: string): boolean;
  4. }
  5. const lettersRegexp = /^[A-Za-z]+$/;
  6. const numberRegexp = /^[0-9]+$/;
  7. export class LettersOnlyValidator implements StringValidator {
  8. isAcceptable(s: string) {
  9. return lettersRegexp.test(s);
  10. }
  11. }
  12. export class ZipCodeValidator implements StringValidator {
  13. isAcceptable(s: string) {
  14. return s.length === 5 && numberRegexp.test(s);
  15. }
  16. }
  1. var Validation;
  2. (function (Validation) {
  3. const lettersRegexp = /^[A-Za-z]+$/;
  4. const numberRegexp = /^[0-9]+$/;
  5. class LettersOnlyValidator {
  6. isAcceptable(s) {
  7. return lettersRegexp.test(s);
  8. }
  9. }
  10. Validation.LettersOnlyValidator = LettersOnlyValidator;
  11. class ZipCodeValidator {
  12. isAcceptable(s) {
  13. return s.length === 5 && numberRegexp.test(s);
  14. }
  15. }
  16. Validation.ZipCodeValidator = ZipCodeValidator;
  17. })(Validation || (Validation = {}));

为外部类库撰写声明文件

不是用TypeScript书写的外部类库,大部分都是通过提供顶级对象的方式使用的,因此使用命名空间来表示它们是一个好办法。例如D3的声明文件可以这么写:

  1. declare namespace D3 {
  2. export interface Selectors {
  3. select: {
  4. (selector: string): Selection;
  5. (element: EventTarget): Selection;
  6. };
  7. }
  8. export interface Event {
  9. x: number;
  10. y: number;
  11. }
  12. export interface Base extends Selectors {
  13. event: Event;
  14. }
  15. }
  16. declare var d3: D3.Base;

历史和不规则的翻译术语

external modules,ES6使用的模块,简称 modules
internal modules,即命名空间,在TS中刚开始使用 module 表示,后用 namespace 表示。
ambient namespace,翻译为外部命名空间,即通过declare定义的namespace,可以全局引用。

通过上面的例子可以看到,internal namespace,既包含了类型、又包含了实现,实现部分会编译成代码;而 ambient namespace 则通常只包含类型声明,不会编译成代码。

因为 internal namespace 的目的是不让变量污染外部,因此要能通过 句柄 访问的声明,必须通过 export 暴露;而 ambient namespace 只是用于描述类型,则无需 export 也照旧可以使用。

仍旧让人困惑的地方 🤔

对于internal modules,编译出来的代码相当于用var定义的全局变量。
但是在前端工程项目中,它不会被作为全局变量暴露出来,即我们在模块中定义使用,都仅限于该模块能感知到。

这就让internal modules的应用场景很难确定?因为:

  • 如果你想要用全局的类型声明,直接 ambient namespace即可,不用包含实现
  • 如果你先要一些全局的函数实现和配置,在项目中通过import模块和 webpack注入环境变量都可以实现

如果一个文件中不包含import,不管namespace定义的是类型,在其他TS文件中,是能感知到 namespace 的存在而直接使用的。这让 multi-file-namespaces 这一节的内容,少了具体的应用场景,即在什么场景下要用 <reference,或许是为了tsc将某个文件编译成js的过程,也需要编译以来的其他文件时?

另外,此前学习有过这么一段记录 “使用命名空间下实际上是创建了全局闭包,因此建议不要和模块一起使用,而是当作全局模块,tsc 生成 .js文件后,通过在html中引用的方式使用” 它的来源出处在哪里呢?

困惑的主题关键字: