TypeScript 3.1 命名空间 namespace
命名的说明:在 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 + "'.");
}
}
运行结果:
二、命名空间
上面只有两个验证器,随着验证器的增加,我们需要一种手段来组织代码,以便于记录他们类型的同时还不用担心与其他对象产生命名冲突。因此,我们将验证器包裹到一个命名空间中,而不是把他们放在全局的命名空间中。
下面的例子中,把所有与验证器有关的类型都放到了一个叫做 Validation
的命名空间里,因为我们想让这些接口和类在命名空间之外也能够访问到,因此需要使用 export
。
相反的,变量 lettersRegexp
和 numberRegexp
其实现的细节,不需要导出,因此它们在命名空间外是不能访问的。在文件末尾的测试代码中,优于是在命名空间之外访问,因此需要限定类型的名称,比如 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
编译结果:
第二种方式,可以编译每一个文件(默认方式,见上面截图效果),每个源文件都会对应生成一个 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;
六、示例代码
本文示例代码:
文章版权:Postbird-There I am , in the world more exciting!
本文链接:http://www.ptbird.cn/typescript-namespace.html
转载请注明文章原始出处 !