在TypeScript中,模块有两层含义:“内部模块”被称为“命名空间”,“外部模块”现在被简称为“模块”(本节要探讨的内容)。这与 ECMAScript 2015中的术语是一致的。
和命名空间一样,模块也包含代码和声明。不同的是,模块可以声明它的依赖。
模块会把依赖添加到模块加载器(如CommonJS/Require.js)上。对于小型的JavaScript应用来说,这种添加可能没必要;但是,对于大型应用来说,这一点点的花费会带来长久的模块化和可维护性上的便利。模块也提供了更好的代码重用、更强的封闭性及更好的使用工具进行优化。
对于Node.js应用来说,模块是默认并推荐的组织代码的方式。
从ECMAScript 2015开始,模块成了语言内置的部分,可以被所有正常的解释引擎所支持。因此,对于新项目来说,推荐使用模块作为组织代码的方式。
导入声明用于从其他模块导入实体,并在当前模块中为它们提供绑定。示例如下:
导入具有给定名称的模块,并为模块本身创建本地绑定。本地绑定被分类为值(表示模块实例)和命名空间(表示类型和命名空间的容器)。示例如下:
本地绑定所指定的名称必须各自引用“给定模块的导出成员集中的实体”。除非使用了指定不同本地名称的子句,否则本地绑定具有与它们所代表的实体相同的名称和分类。
示例如下:
以下两种导入声明的方式完全相同:
以下导入声明的方式适用于导入给定模块而不创建任何本地绑定(仅当导入的模块有副作用时才有用)。
存在导入Require声明,是为了方便兼容早期版本的TypeScript。
导入Require声明引入了引用给定模块的本地标识符。导入Require声明中指定的字符串文字被解释为模块名称。声明引入的本地标识符成为从引用模块导出的实体的别名,并且与其类别完全相同。具体来说,如果引用的模块不包含导出分配,则标识符将被分类为值和命名空间;如果引用的模块包含导出分配,则标识符的分类与导出分配中指定的实体完全相同。
以下是一个导入Require声明的例子。
相当于ECMAScript 2015中的如下导入声明。
任何声明(如变量、函数、类、类型别名或接口)都能够通过添加关键字“export”来导出。以下是一个导出声明的简单例子。
存在导出分配,是为了兼容早期版本的TypeScript。导出分配将模块成员指定为要导出的实体来代替模块本身。
假设以下示例位于文件point.ts中。
当point.ts被另一个模块导入时,导入别名“Pt”将引用导出的类。此时“Pt”既可以被用作类型,也可以被用作构造函数。
在这里,并不要求导入别名(Pt)与导出实体(Point)具有相同的名称。
在CommonJS模块(http://www.commonjs.org/specs/modules/1.0/)中定义了编写具有隐含隐私的JavaScript模块的方法、导入其他模块的能力及显式导出成员的能力。CommonJS兼容系统提供的“require”功能,可用于同步加载其他模块以获取其单例模式实例,以及导出变量,模块可以向其添加属性以定义其外部API。
下面是一个在TypeScript中编写的示例。
上面的示例使用了CommonJS模块,此时两个.ts文件会被编译成两个JavaScript文件。一个是main.js,如下:
另一个是log.js,如下:
在生成的JavaScript文件中可以看到,模块导入声明使用了“require”函数。仅当导入的模块或引用导入模块的本地别名在导入模块的主体中的某处引用为PrimaryExpression时,才会为特定的导入模块发出变量声明和“require”调用。如果导入的模块仅作为NamespaceName或TypeQueryExpression引用,则不会起效。
TypeScript提供了AMD(Asynchronous Module Definition,异步模块定义)模式。AMD模式遵循AMD规范(https://github.com/amdjs/amdjs-api/wiki/AMD),该规范扩展了CommonJS模块的规范,可以异步加载所依赖的模块。在使用AMD模式时,使用关键字“define”来定义回调函数。
下面是一个在TypeScript中编写的示例。
上面的“main”和“log”示例在使用AMD模式编译时将生成以下JavaScript代码。
文件main.js:
文件log.js:
define函数可以根据需要将要添加的依赖项添加到数组和参数列表中,用以表示导入的模块。其行为与CommonJS模块类似:仅当导入的模块在导入模块的主体中的某处被引用为PrimaryExpression时,才会为特定的导入模块生成依赖项。如果导入的模块仅作为NamespaceName引用,则不会为该模块生成依赖项。