本节介绍命名空间相关内容,包括命名空间的作用、实现方式、注意事项、命名空间和模块的区别等内容。
由于全局作用域下,有可能会导致命名冲突,使用命名空间之后就可以通过命名空间组织代码,将代码封装到不同的命名空间当中,这样就不会出现命名冲突的问题。
-
讲解视频
TS学习笔记十三:命名空间
TS学习笔记十四:命名空间和模块
-
B站视频
TS学习笔记十三:命名空间
TS学习笔记十四:命名空间和模块
-
西瓜视频
https://www.ixigua.com/7323775599850291738
一、单文件命名空间
interface str{
}
const a:number = 1;
export class str1 implements str{}
export class str2 implements str{}
上述实例若要继续扩展str时就需要对代码进行组织,以便于在记录他们的类型同时还不用担心与其它对象产生命名冲突,可以把定义包裹到一个命名空间中,使用命名空间中,可以定义哪些接口是外部可以访问的,需要使用export进行导出,内部的变量是实现的具体细节,不需要导出,因此在命名空间外部是不能访问的,由于是在命名空间之外访问,因此需要限定类型的名,如A.str,示例如下:
namespace A{
export interface str{
}
const a:number = 1;
export class str1 implements str{}
export class str2 implements str{}
}
//通过A.str/A.str1/A.str2这种方式进行命名空间的调用
二、多文件中使用
命名空间可以被分割到多个文件中,这样如果代码量比较大的情况下,就可以根据功能进行文件划分,提高代码的可维护性。
命名空间在不同的文件中,仍然是同一个命名空间,使用时和在同一个文件中定义的命名空间一样,因为不同的文件之间存在依赖关系,所以需要使用引用标签告诉编译器文件之间的关联,如下:
A.ts
namespace V{
export interface str{}
}
B.ts
namespace V{
const a:number = 1;
export calss str1 implements str{}
}
C.ts
namespace V{
const b:number = 2;
export class str2 implements str{}
}
test.ts
//调用时V.str/V.str1/V.str2通过这种方式调用,和在同一个文件中的使用方式一致。
当设计多个文件时,必须确保所有编译后的代码都被加载了,有两种方式可以实现:
- 把所有的文件编译为一个输出文件,需要使用–outFile标记:
tsc --outFIle test.js test.ts
编译器会根据源码中的引用标签自动地对输出进行排序,也可以单独的指定每个文件。
2. 可以编译每一个文件,每个源文件都会生成一个js文件,在html上通过
三、别名
命名空间的访问需要A.B.C类似这样按照层级访问对应的内容,如果内容层级过多时使用起来就会很麻烦,可以使用起一个比较短的别名解决此问题,方法是import a = A.B.C,此时就可以直接使用a访问了,而不用A.B.C进行访问。此处的import a = 和加载模块的import x = require(‘A’)的语法是有区别的,此语法是为指定的符号创建一个别名,可以用此种方式给任意标识符创建别名,也包括导入的模块中的对象。
namespace A{
export namespace B{
export class C{}
export class D{}
}
}
import a = A.B;
let c = new a.C();
上述示例中直接使用导入符号限定名赋值,并没有使用require关键字,和使用var相似,也适用于类型和导入的具有命名空间含义的符号,对于值来说,import 会生成与原始符号不同的引用,所以改变别名的var值并不会影响原始变量的值。
四、使用其它JS库
有时候会使用不是ts编写的类库的类型,需要声明类库导出的API,由于大部分程序库只提供少数的顶级对象,此时可以用命名空间来表示。
此处的声明不是外部程序的具体实现,而只是类型等的声明,需要编写在.d.ts文件中,类似于C语言中的.h文件。
五、外部命名空间
许多库都是在全局对象定义它的功能,可以通过
declare namespace ${
export interface Selecters{
select:{
(selector:string):Selection;
(element:EventTarget):Selection;
}
}
export interface Event{
x:number;
y:number;
}
export interface Base extends Selecters{
event:Event;
}
}
declare let a :$.Base;
六、命名空间及模块
命名空间和模块具有不同的使用场景及规则,ts1.5中为了和es6中的术语保持一致,术语名发生了变化,内部模块现在被称做命名空间,外部模块被简称为模块,module x{}相当于现在推荐的写法namespace X{}
1.使用命名空间
命名空间是位于全局命名空间下的一个普通带有名字的js对象,可以在多文件中使用,并通过–outFIle结合在一起,可以组织应用的代码结构,但是如果前端项目比较庞大时,很难识别命名空间之间的依赖关系。
2.使用模块
模块和命名空间一样,模块可以包含代码和声明,不同的是模块可以声明它的依赖,模块会把依赖添加到模块加载器上,对于小型JS应用来说可能没必要,但是对大型应用就会使得应用具有较高的可维护性,模块也提供了更好的代码重用,更强的封闭性以及更好的使用工具进行优化。
Node.js中模块是默认并推荐的组织代码的方式,从ES6开始,模块成为了语音内置的部分,应该会被所有正常的解释引擎所支持。
3.常见陷阱
(1) 对模块使用///
不能使用///引用模块文件,应该使用import。使用import的时候编译器会首先尝试去查找相应路径下的.ts/.tsx/.d.ts,如果这些文件都找不到,编译器会查找外部模块声明。
(2) 不必要的命名空间
不同的模块永远也不会在相同的作用域内使用相同的名字,因为使用模块时会为他们命名,所以完全没有必要把导出的符号包裹在一个命名空间里,如下:
a.ts
export namespace A{
export class T{}
export class B{}
}
使用时如下:
import * as a from “./a”;
let t = new a.A.T();
顶层的模块包括了T和B,使用起来就比较麻烦,需要多写一层。可以对上述示例进行改进:
a.ts
export class T{}
export class B{}
b.ts
import * as a from “./a”
let t = new a.T();
不应该对模块使用命名空间,使用命名空间是为了逻辑分组和避免命名冲突,模块文件本身已经是一个逻辑分组,并且它的名字是由导入这个模块的代码指定,所以没有必要为导出的对象增加额外的模块层。
(3) 模块的取舍
ts中模块文件与生成的js文件也是一一对应的,当指定的目标模块系统不同,可能无法链接多个模块源文件,如目标模块系统为commonjs或umd时,无法使用outFIle选项,ts1.8以上目标是amd或system时也能够使用outFIle。