TypeScript 命名空间

841 阅读4分钟

1.前言

在TypeScript官方文档中是这样描述命名空间的:
请务必注意一点,TypeScript 1.5里术语名已经发生了变化。 “内部模块”现在称做“命名空间”。 “外部模块”现在则简称为“模块”,这是为了与 ECMAScript 2015里的术语保持一致,(也就是说 module X { 相当于现在推荐的写法 namespace X {)。
这段话简单看就是,现在“内部模块”被叫命名空间,使用的关键词为namespace;“外部模块”现在被叫做模块,使用关键词module

2.命名空间的使用

命名空间的使用方式非常简单:

namespace XXX {}

在官方文档中namespace的使用例子如下

//创建一个命名空间Validation
namespace Validation {
   //将命名空间中的接口暴露出去
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }

    const lettersRegexp = /^[A-Za-z]+$/;
    const numberRegexp = /^[0-9]+$/;
    //将LettersOnllyValidator类暴露出去
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }
    //将ZipCodeValidator类暴露出去
    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的命名空间,在这个命名空间中有一个StringValidator接口,并有两个类分别是LettersOnlyValidator类和ZipCodeValidator类,这两个类分别用来判断是否为英文字母和是否为0-9的数字。
  • 当我们想让这些接口和类在命名空间之外也是可访问的,所以需要使用 export。 相反的,变量 lettersRegexpnumberRegexp是实现的细节,不需要导出,因此它们在命名空间外是不能访问的。 在文件末尾的测试代码里,由于是在命名空间之外访问,因此需要限定类型的名称,比如 Validation.LettersOnlyValidator

从上面的话中我们可以看出,在命名空间中,可以隔绝作用域,外部的作用域无法访问到Validation中的作用域,如果想让外面的作用域访问到Validation中的作用域,需要使用export将想要访问的接口或是类给暴露出去。

  • 在命名空间中,有他自己的作用域,外部无法访问到,要想访问需使用export将访问的部分暴露出去

3.每个命名空间中的作用域都是独立的

// namespace 定义一个名为 A 的单独的空间
namespace A {
    interface Animal{
        name:string;
        eat():void;
    }

    // 外部需要调用的类 需要用 【export】 给 导出 去
    export class Dog implements Animal {
        name: string;
        constructor(name:string) {
            this.name = name;
        }
        eat() {
            return this.name + '吃骨头1'
        }
        
    }
}

// namespace 定义一个名为 V 的单独的空间
namespace V {
    interface Animal{
        name:string;
        eat():void;
    }

    // 外部需要调用的类 需要用 【export】 给 导出 去
    export class Dog implements Animal {
        name: string;
        constructor(name:string) {
            this.name = name;
        }
        eat() {
            return this.name + '吃骨头2'
        }
        
    }
}

let dog1 = new A.Dog('小黑')
console.log(dog1.eat())  // 小黑吃骨头1

let dog2  = new V.Dog('小白')
console.log(dog2.eat())  // 小白吃骨头2

从上例子可以看出,每个命名空间都是相互独立的

4. 同名命名空间的合并

namespace Animals {
    export class Cat { }
}

namespace Animals {
    export interface Legged { numberOfLegs: number; }
    export class Dog { }
}

以上代码等同于

namespace Animals {
    export interface Legged { numberOfLegs: number; }

    export class Cat { }
    export class Dog { }
}

在相同的名字的命名空间中

  1. 其中模块导出的同名接口会合并为一个接口
  2. 未导出的成员,仅在未合并前的命名空间可见。从其他空间合并来的成员无法访问未导出的成员
  3. 对于里头值的合并,如果里头值的名字相同,那么后来的命名空间的值会优先级会更高
  4. 对于没有冲突的成员,会直接混入

5.总结

  1. 命名空间有自己的作用域,外部无法访问到。当命名空间使用export暴露出去时,外部可以访问到暴露的部分。当使用export将命名空间整个暴露出去时,外部需要使用的类或接口也要使用export在命名空间中暴露出去

对于同名命名空间来说

  1. 其中模块导出的同名接口会合并为一个接口
  2. 未导出的成员,仅在未合并前的命名空间可见。从其他空间合并来的成员无法访问未导出的成员
  3. 对于里头值的合并,如果里头值的名字相同,那么后来的命名空间的值会优先级会更高
  4. 对于没有冲突的成员,会直接混入