命名的说明:在 TypeScript 1.5 里术语名称已经发生了变化。 “内部模块” 现在称作 “命名空间”,“外部模块”现在则简称为“模块”,这是为了和 ES6 的术语保持一致,也就是说 module X { 相当于现在的 namespace X{

一、提供基础的文章和代码示例

下面的代码在整个文章中都会用到,主要是几个简单的字符串的验证器,可以用来验证表单里的用户输入或者是验证外部的数据:

所有的验证器都放在一个文件里:

// 所有的验证器都放在一个文件里

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", "8288", "123"];

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

编译器编译的结果如下:

// 所有的验证器都放在一个文件里
var lettersRegexp = /^[A-Za-z]+$/;
var numberRegexp = /^[0-9]+$/;
var LettersOnlyValidator = /** @class */ (function () {
    function LettersOnlyValidator() {
    }
    LettersOnlyValidator.prototype.isAcceptable = function (s) {
        return lettersRegexp.test(s);
    };
    return LettersOnlyValidator;
}());
var ZipCodeValidator = /** @class */ (function () {
    function ZipCodeValidator() {
    }
    ZipCodeValidator.prototype.isAcceptable = function (s) {
        return s.length === 5 && numberRegexp.test(s);
    };
    return ZipCodeValidator;
}());
// 一些简单的测试
var strings = ["Hello", "8288", "123"];
var validators = {};
validators['ZIP code'] = new ZipCodeValidator();
validators['Letters only'] = new LettersOnlyValidator();
for (var _i = 0, strings_1 = strings; _i < strings_1.length; _i++) {
    var s = strings_1[_i];
    for (var name_1 in validators) {
        var isMatch = validators[name_1].isAcceptable(s);
        console.log("'" + s + "' " + (isMatch ? "matches" : "does not match") + " '" + name_1 + "'.");
    }
}

运行结果:

4.jpg

二、命名空间

上面只有两个验证器,随着验证器的增加,我们需要一种手段来组织代码,以便于记录他们类型的同时还不用担心与其他对象产生命名冲突。因此,我们将验证器包裹到一个命名空间中,而不是把他们放在全局的命名空间中。

下面的例子中,把所有与验证器有关的类型都放到了一个叫做 Validation 的命名空间里,因为我们想让这些接口和类在命名空间之外也能够访问到,因此需要使用 export

相反的,变量 lettersRegexpnumberRegexp 其实现的细节,不需要导出,因此它们在命名空间外是不能访问的。在文件末尾的测试代码中,优于是在命名空间之外访问,因此需要限定类型的名称,比如 Validator.LettersOnlyValidator

使用命名空间的验证器

// 使用命名空间的验证器

namespace Validator {
    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 lettersRegexp.test(s);
        }
    }

}

// 测试

let strings = ['Hello', '89772', '1230'];

let validators: { [s: string]: Validator.StringValidator; } = {};

validators['ZIP code'] = new Validator.ZipCodeValidator();
validators['Letters Only'] = new Validator.LettersOnlyValidator();

for (let s of string) {
    for (let name in validators) {
        console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`);
    }
}

编译结果:

// 使用命名空间的验证器
var Validator;
(function (Validator) {
    var lettersRegexp = /^[A-Za-z]+$/;
    var numberRegexp = /^[0-9]+$/;
    var LettersOnlyValidator = /** @class */ (function () {
        function LettersOnlyValidator() {
        }
        LettersOnlyValidator.prototype.isAcceptable = function (s) {
            return lettersRegexp.test(s);
        };
        return LettersOnlyValidator;
    }());
    Validator.LettersOnlyValidator = LettersOnlyValidator;
    var ZipCodeValidator = /** @class */ (function () {
        function ZipCodeValidator() {
        }
        ZipCodeValidator.prototype.isAcceptable = function (s) {
            return lettersRegexp.test(s);
        };
        return ZipCodeValidator;
    }());
    Validator.ZipCodeValidator = ZipCodeValidator;
})(Validator || (Validator = {}));
// 测试
var strings = ['Hello', '89772', '1230'];
var validators = {};
validators['ZIP code'] = new Validator.ZipCodeValidator();
validators['Letters Only'] = new Validator.LettersOnlyValidator();
for (var _i = 0, string_1 = string; _i < string_1.length; _i++) {
    var s = string_1[_i];
    for (var name_1 in validators) {
        console.log("\"" + s + "\" - " + (validators[name_1].isAcceptable(s) ? "matches" : "does not match") + " " + name_1);
    }
}

可以发现,使用了 NameSpace 之后, Validator 实际上通过一个立即执行函数赋值了两个属性。

三、分离到多文件

当应用变得越来越大的时候。我们需要将代码分离到不同的文件中以便于维护。

多文件命名空间

现在将上面的 Validator 分隔成多个文件,尽管是不同的文件但是他们仍然是同一个命名空间,并且在使用的时候就如同它们在一个文件中顶一个的一样。

因为不同的文件之间存在依赖关系,所以加入了引用标签来告诉编译器文件之间的关联。

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

ZipCodeVlidator.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 标记

tsc --outFile sample.js Test.ts

编译结果:

/// <reference path="Validation.ts" />
var Validation;
(function (Validation) {
    var lettersRegexp = /^[A-Za-z]+$/;
    var LettersOnlyValidator = /** @class */ (function () {
        function LettersOnlyValidator() {
        }
        LettersOnlyValidator.prototype.isAcceptable = function (s) {
            return lettersRegexp.test(s);
        };
        return LettersOnlyValidator;
    }());
    Validation.LettersOnlyValidator = LettersOnlyValidator;
})(Validation || (Validation = {}));
/// <reference path="Validation.ts" />
var Validation;
(function (Validation) {
    var numberRegexp = /^[0-9]+$/;
    var ZipCodeValidator = /** @class */ (function () {
        function ZipCodeValidator() {
        }
        ZipCodeValidator.prototype.isAcceptable = function (s) {
            return s.length === 5 && numberRegexp.test(s);
        };
        return ZipCodeValidator;
    }());
    Validation.ZipCodeValidator = ZipCodeValidator;
})(Validation || (Validation = {}));
/// <reference path="Validation.ts" />
/// <reference path="LettersOnlyValidator.ts" />
/// <reference path="ZipCodeValidator.ts" />
// Some samples to try
var strings = ["Hello", "98052", "101"];
// Validators to use
var validators = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
// Show whether each string passed each validator
for (var _i = 0, strings_1 = strings; _i < strings_1.length; _i++) {
    var s = strings_1[_i];
    for (var name_1 in validators) {
        console.log("\"" + s + "\" - " + (validators[name_1].isAcceptable(s) ? "matches" : "does not match") + " " + name_1);
    }
}

编译器会根据源码里的引用标签自动对输出进行排序,也可以单独的指定每个文件。

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

编译结果:

5.jpg

第二种方式,可以编译每一个文件(默认方式,见上面截图效果),每个源文件都会对应生成一个 JavaScript 文件,然后在页面上通过 <script> 标签把所有的 Javascript 文件按正确顺序引入,比如:

 <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()"

编译后:

// 别名
var Shapes;
(function (Shapes) {
    var Polygons;
    (function (Polygons) {
        var Triangle = /** @class */ (function () {
            function Triangle() {
            }
            return Triangle;
        }());
        Polygons.Triangle = Triangle;
        var Square = /** @class */ (function () {
            function Square() {
            }
            return Square;
        }());
        Polygons.Square = Square;
    })(Polygons = Shapes.Polygons || (Shapes.Polygons = {}));
})(Shapes || (Shapes = {}));
var polygons = Shapes.Polygons;
var sq = new polygons.Square(); // Same as "new Shapes.Polygons.Square()"

注意,这里并没有使用 require 关键字,而是直接导入符号的限定名赋值。这和使用 var 类似,但是它还适用于类型和导入的具有命名空间含义的符号。重要的是,对于值来讲,import 会生成与原始符号不同的引用,所以改变别名的 var 值,不会影响原始变量的值。

五、使用其他的 JavaScript 库

为了描述不是 TypeScript 写的类库的类型,需要声明类库导出的 API 。由于大部分程序库只提供少数的顶级对象,命名空间是用来表示它们的好办法。

我们称其为声明是因为它不是外部程序的具体实现。我们通常在 .d.ts 里写这些声明。如果你熟悉 C/C++,你可以当做 .h 文件。

外部命名空间

流行的程序库 D3 在全局对象 d3 里定义它的功能。因为这个库通过一个 script 标签加载(不是通过模块加载器),它的声明文件使用内部模块来定义它的类型。为了让 TypeScript 编译器识别它的类型,我们使用外部命名空间声明。比如:

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;

六、示例代码

本文示例代码: