[TypeScript] 编程实践之1: Google的TypeScript代码风格11:Script和Modules

11 Scripts和Modules

TypeScript实现对ECMAScript 2015模块的支持,并支持针对CommonJS,AMD和其他模块系统的下层代码生成。

11.1 程序和源文件

TypeScript程序由一个或多个源文件组成。

SourceFile:
	ImplementationSourceFile
	DeclarationSourceFile

ImplementationSourceFile:
	ImplementationScript
	ImplementationModule

DeclarationSourceFile:
	DeclarationScript
	DeclarationModule

扩展名为“ .ts”的源文件是包含语句和声明的实现源文件,扩展名为“ .d.ts”的源文件是仅包含声明的声明源文件。

声明源文件是实现源文件的严格子集,用于以附加方式声明与现有JavaScript代码关联的静态类型信息。 它们完全是可选的,但是使TypeScript编译器和工具能够在将现有的JavaScript代码和库集成到TypeScript应用程序中时提供更好的验证和帮助。

编译TypeScript程序时,该程序的所有源文件都会一起处理。 不同源文件中的语句和声明可以相互依赖,可能以循环方式相互依赖。 默认情况下,为编译中的每个实现源文件生成一个JavaScript输出文件,但是声明源文件中不生成任何输出。

11.1.1 源文件依赖

TypeScript编译器自动确定源文件的依赖关系,并将这些依赖关系包含在正在编译的程序中。根据“参考注释”和模块导入声明进行确定,如下所示:

  • 形式的注释出现在源文件中的第一个标记添加对path参数中指定的源文件的依赖之前。相对于包含源文件的目录的路径被解析。
  • 指定一个相对模块名称的模块导入声明(第11.3.1节)解析相对于所包含源文件目录的名称。如果存在带有结果路径和文件扩展名“ .ts”的源文件,则将该文件作为依赖项添加。否则,如果存在带有结果路径和文件扩展名“ .d.ts”的源文件,则将该文件添加为依赖项。
  • 指定顶级模块名称的模块导入声明(第11.3.1节)以主机相关的方式解析名称(通常是通过相对于模块命名空间根解析名称,或在一系列目录中搜索名称) 。如果找到具有与引用相对应的扩展名“ .ts”或“ .d.ts”的源文件,则将该文件添加为依赖项。

依次将包含为依赖项的任何文件以引用方式分析其引用,直到确定所有依赖项为止。

11.2 Script

不包含模块导入或导出声明的源文件被分类为脚本。 脚本形成单个全局命名空间,并且脚本中声明的实体在程序中的所有范围内。

ImplementationScript:
	ImplementationScriptElementsopt

ImplementationScriptElements:
	ImplementationScriptElement
	ImplementationScriptElements ImplementationScriptElement

ImplementationScriptElement:
	ImplementationElement
	AmbientModuleDeclaration

ImplementationElement:
	Statement
	LexicalDeclaration
	FunctionDeclaration
	GeneratorDeclaration
	ClassDeclaration
	InterfaceDeclaration
	TypeAliasDeclaration
	EnumDeclaration
	NamespaceDeclaration
	AmbientDeclaration
	ImportAliasDeclaration

DeclarationScript:
	DeclarationScriptElementsopt

DeclarationScriptElements:
	DeclarationScriptElement
	DeclarationScriptElements DeclarationScriptElement

DeclarationScriptElement:
	DeclarationElement
	AmbientModuleDeclaration

DeclarationElement:
	InterfaceDeclaration
	TypeAliasDeclaration
	NamespaceDeclaration
	AmbientDeclaration
	ImportAliasDeclaration

组成全局命名空间的脚本的初始化顺序最终取决于在运行时加载生成的JavaScript文件的顺序(例如,可以由引用生成的JavaScript文件的

11.3 Modules

包含至少一个模块导入或导出声明的源文件被视为单独的模块。 在模块中声明的非导出实体仅在该模块的作用域内,但是可以使用导入声明将导出的实体导入其他模块。

ImplementationModule:
	ImplementationModuleElementsopt

ImplementationModuleElements:
	ImplementationModuleElement
	ImplementationModuleElements ImplementationModuleElement

ImplementationModuleElement:
	ImplementationElement
	ImportDeclaration
	ImportAliasDeclaration
	ImportRequireDeclaration
	ExportImplementationElement
	ExportDefaultImplementationElement
	ExportListDeclaration
	ExportAssignment

DeclarationModule:
	DeclarationModuleElementsopt

DeclarationModuleElements:
	DeclarationModuleElement
	DeclarationModuleElements DeclarationModuleElement

DeclarationModuleElement:
	DeclarationElement
	ImportDeclaration
	ImportAliasDeclaration
	ExportDeclarationElement
	ExportDefaultDeclarationElement
	ExportListDeclaration
	ExportAssignment

模块的初始化顺序由所使用的模块加载程序确定,而TypeScript语言未指定。 但是,通常情况下,非循环相关的模块会以正确的顺序自动加载和初始化。

另外,可以在声明脚本中使用AmbientModuleDeclarations声明模块,该脚本直接将模块名称指定为字符串文字。 第12.2节将对此进行进一步描述。

下面是用单独的源文件编写的两个模块的示例:

// -------- main.ts --------  
import { message } from "./log";  
message("hello");

// -------- log.ts --------  
export function message(s: string) {  
    console.log(s);  
}

“ main”模块中的导入声明引用了“ log”模块,编译“ main.ts”文件会使“ log.ts”文件也作为程序的一部分进行编译。

TypeScript支持模块的多种JavaScript代码生成模式:

  • CommonJS。服务器框架(例如node.js)使用此格式。
  • AMD(异步模块定义)。异步模块加载程序(例如RequireJS)使用此格式。
  • UMD(通用模块定义)。 AMD格式的一种变体,它允许CommonJS加载程序也加载模块。
  • 系统。此格式用于在下层环境中以高保真度表示ECMAScript 2015语义。

所需的模块代码生成模式是通过编译器选项选择的,并且不会影响TypeScript源代码。确实,有可能编写可被编译以在服务器端(例如,使用node.js)和客户端(使用AMD兼容加载程序)上使用的模块,而无需更改TypeScript源代码。

11.3.1 Module命名

使用模块名称标识和引用模块。以下定义与CommonJS Modules 1.0规范中提供的定义一致。

  • 模块名称是一串用正斜杠分隔的术语。
  • 模块名称可能没有文件扩展名,例如“ .js”。
  • 模块名称可以是相对的或顶级的。如果第一个术语是“”,则模块名称是相对的。要么 ”…”。
  • 顶级名称是从概念模块命名空间根目录解析的。
  • 相对名称是相对于它们所在的模块的名称来解析的。

为了解决模块引用,TypeScript将文件路径与每个模块关联。文件路径仅是模块源文件的路径,而没有文件扩展名。例如,包含在源文件“ C:\ src \ lib \ io.ts”中的模块具有文件路径“ C:/ src / lib / io”,而包含在源文件“ C:\ src \ ui \ editor.d.ts”的文件路径为“ C:/ src / ui / editor”。

导入声明中的模块名称解析如下:

  • 如果导入声明指定了相对模块名称,则相对于引用模块文件路径的目录来解析该名称。该程序必须包含具有结果文件路径的模块,否则会发生错误。例如,在文件路径为“ C:/ src / ui / main”的模块中,模块名称“ ./editor”和“ …/lib/io”引用的文件路径为“ C:/ src / ui / editor”和“ C:/ src / lib / io”。
  • 如果导入声明指定了顶级模块名称,并且程序包含AmbientModuleDeclaration(第12.2节),其字符串文字指定了该确切名称,则导入声明将引用该环境模块。
  • 如果导入声明指定顶级模块名称,并且程序不包含带有指定该确切名称的字符串文字的AmbientModuleDeclaration(第12.2节),则以主机相关的方式解析名称(例如,通过考虑相对于名称的名称)模块命名空间根目录)。如果找不到匹配的模块,则会发生错误。

11.3.2 导入声明

导入声明用于从其他模块导入实体,并在当前模块中为其提供绑定。

表格的进口声明

import * as m from "mod";

导入具有给定名称的模块,并为模块本身创建本地绑定。 本地绑定分为一个值(代表模块实例)和一个命名空间(代表类型和命名空间的容器)。

表格的进口声明

import { x, y, z } from "mod";

导入给定的模块并为模块的导出成员的指定列表创建本地绑定。 指定的名称必须每个都引用给定模块的导出成员集中(11.3.4.4)中的一个实体。 本地绑定与其表示的实体具有相同的名称和分类,除非使用as子句指定不同的本地名称:

import { x as a, y as b } from "mod";

表格的进口声明

import d from "mod";

与进口申报完全相同

import { default as d } from "mod";

表格的进口声明

import "mod";

导入给定的模块而不创建任何本地绑定(仅在导入的模块有副作用的情况下才有用)。

11.3.3 Import Require声明

存在导入要求声明以实现与早期版本的TypeScript的向后兼容性。

ImportRequireDeclaration:
	import BindingIdentifier = require ( StringLiteral ) ;

导入require声明引入了引用给定模块的本地标识符。 导入require声明中指定的字符串文字将解释为模块名称(第11.3.1节)。 声明引入的本地标识符成为从引用模块导出的实体的别名,并按照与之完全相同的方式进行分类。 具体来说,如果引用的模块不包含导出分配,则将标识符分类为值和命名空间,如果引用的模块包含导出分配,则将标识符进行分类,就像在导出分配中命名的实体一样。

导入需要以下形式的声明

import m = require("mod");

等效于ECMAScript 2015导入声明

import * as m from "mod";

前提是所引用的模块不包含导出分配。

11.3.4 Export声明

导出声明声明一个或多个导出模块成员。 可以使用导入声明(11.3.2)将模块的导出成员导入其他模块。

11.3.4.1 Export修饰符

在模块的主体中,声明可以通过包含export修饰符来导出声明的实体。

ExportImplementationElement:
	export VariableStatement
	export LexicalDeclaration
	export FunctionDeclaration
	export GeneratorDeclaration
	export ClassDeclaration
	export InterfaceDeclaration
	export TypeAliasDeclaration
	export EnumDeclaration
	export NamespaceDeclaration
	export AmbientDeclaration
	export ImportAliasDeclaration

ExportDeclarationElement:
	export InterfaceDeclaration
	export TypeAliasDeclaration
	export AmbientDeclaration
	export ImportAliasDeclaration

除了在模块的本地声明空间中引入名称之外,导出的声明还在模块的导出声明空间中引入具有相同分类的相同名称。 例如,声明

export function point(x: number, y: number) {  
    return { x, y };  
}

引入了引用该函数的本地名称点和导出的名称点。

11.3.4.2 Export Default声明

导出默认声明为导出名为default的实体提供了简化的语法。

ExportDefaultImplementationElement:
	export default FunctionDeclaration
	export default GeneratorDeclaration
	export default ClassDeclaration
	export default AssignmentExpression ;

ExportDefaultDeclarationElement:
	export default AmbientFunctionDeclaration
	export default AmbientClassDeclaration
	export default IdentifierReference ;

函数,生成器或类的ExportDefaultImplementationElement或ExportDefaultDeclarationElement会在包含模块的导出声明空间中引入一个名为default的值,对于类而言,则引入一个名为default的类型。 声明可以选择为导出的函数,生成器或类指定本地名称。 例如,声明

export default function point(x: number, y: number) {  
    return { x, y };  
}

引入了引用该函数的本地名称点和默认导出名称。 该声明实际上等效于

function point(x: number, y: number) {  
    return { x, y };  
}

export default point;

再次等同于

function point(x: number, y: number) {  
    return { x, y };  
}

export { point as default };

由单个标识符组成的表达式的ExportDefaultImplementationElement或ExportDefaultDeclarationElement必须命名在当前模块或全局命名空间中声明的实体。 该声明在包含模块的导出声明空间中引入了一个名为default的实体,其类别与引用的实体相同。 例如,声明

interface Point {  
    x: number;  
    y: number;  
}

function Point(x: number, y: number): Point {  
    return { x, y };  
}

export default Point;

引入一个本地名称Point和一个默认的导出名称,同时具有值和类型含义。

除单个标识符外,任何表达式的ExportDefaultImplementationElement都会在包含模块的导出声明空间中引入一个名为default的值。 例如,声明

export default "hello";

引入一个名为default的字符串类型的导出值。

11.3.4.3 Export list声明

导出列表声明从当前模块或指定模块中导出一个或多个实体。

ExportListDeclaration:
	export * FromClause ;
	export ExportClause FromClause ;
	export ExportClause ;

不包含FromClause的ExportListDeclaration将从当前模块中导出实体。 在表格的声明中

export { x };

名称x必须引用在当前模块或全局命名空间中声明的实体,并且声明在包含模块的导出声明空间中引入具有相同名称和含义的实体。

带有FromClause的ExportListDeclaration从指定模块重新导出实体。 在表格的声明中

export { x } from "mod";

名称x必须引用指定模块的导出成员集中的实体,并且声明在包含模块的导出声明空间中引入具有相同名称和含义的实体。 没有为x创建本地绑定。

ExportListDeclaration的ExportClause可以指定多个实体,还可以选择指定用于导出实体的不同名称。 例如,声明

export { x, y as b, z as c };

在包含模块的导出声明空间中引入名为x,b和c的实体,其含义分别与分别名为x,y和z的本地实体相同。

指定*而不是ExportClause的ExportListDeclaration称为导出星形声明。 导出星号声明会重新导出指定模块的所有成员。

export * from "mod";

显式导出的成员优先于使用导出星形声明重新导出的成员,如下节所述。

11.3.4.4 导出成员集

通过从一组空的成员E和一组空的已处理模块P开始,然后按如下所述处理模块,以形成E中的全部已导出成员,来确定特定模块的导出成员集。 M包含以下步骤:

  • 将M添加到P。
  • 将M的出口声明空间中的每个成员添加到E中,其名称尚未在E中。
  • 对于M中的每个出口星形声明,如果声明的模块不在P中,则按声明的顺序进行处理。

模块的实例类型是一种对象类型,该对象类型具有模块导出成员集中每个成员的表示值的属性。

如果模块包含导出分配,则该模块也包含导出声明是错误的。 两种出口是互斥的。

11.3.5 Export赋值

存在导出分配是为了与TypeScript的早期版本向后兼容。 导出分配将模块成员指定为要导出的实体,以代替模块本身。

ExportAssignment:
	export = IdentifierReference ;

可以使用导入需求声明(11.3.3)导入包含导出分配的模块,然后导入需求声明引入的本地别名将具有导出分配中命名的标识符的所有含义。

包含导出分配的模块也可以使用常规导入声明(11.3.2)导入,前提是导出分配中引用的实体声明为命名空间或带有类型注释的变量。

假设以下示例位于文件“ point.ts”中:

export = Point;

class Point {  
    constructor(public x: number, public y: number) { }  
    static origin = new Point(0, 0);  
}

当在另一个模块中导入“ point.ts”时,导入别名引用导出的类,并且可以用作类型和构造函数:

import Pt = require("./point");

var p1 = new Pt(10, 20);  
var p2 = Pt.origin;

注意,不要求导入别名使用与导出实体相同的名称。

11.3.6 CommonJS模块

CommonJS Modules定义指定了一种方法,该方法用于编写具有隐式隐私,可以导入其他模块以及显式导出成员的JavaScript模块。 符合CommonJS的系统提供了一个’require’函数,该函数可用于同步加载其他模块以获取其单例模块实例,以及一个’exports’变量,模块可以向其添加属性以定义其外部API。

上面的11.3节中的“ main”和“ log”示例在为CommonJS Modules模式编译时生成以下JavaScript代码:

文件main.js:

var log_1 = require("./log");  
log_1.message("hello");

文件log.js:

function message(s) {  
    console.log(s);  
}  
exports.message = message;

模块导入声明在生成的JavaScript中表示为变量,该变量通过调用模块系统主机提供的“ require”函数进行初始化。 仅当导入模块或引用导入模块的本地别名(第10.3节)被引用为导入模块主体中的PrimaryExpression时,才会为特定的导入模块生成变量声明和’require’调用。 如果仅将导入的模块引用为NamespaceName或TypeQueryExpression,则不会生成任何内容。

一个例子:

文件geometry.ts:

export interface Point { x: number; y: number };

export function point(x: number, y: number): Point {  
    return { x, y };  
}

game.ts文件:

import * as g from "./geometry";  
let p = g.point(10, 20);

“游戏”模块在表达式中(通过其别名“ g”)引用导入的“几何”模块,因此在生成的JavaScript中包含了“要求”调用:

var g = require("./geometry");  
var p = g.point(10, 20);

取而代之的是将“游戏”模块写为仅在类型位置引用“geometry”:

import * as g from "./geometry";  
let p: g.Point = { x: 10, y: 20 };

生成的JavaScript将不依赖于’geometry’模块,而仅仅是

var p = { x: 10, y: 20 };

11.3.7 AMD模块

异步模块定义(AMD)规范扩展了CommonJS Modules规范,提供了一种模式,用于编写具有相关性的异步可加载模块。 使用AMD模式,模块将作为对全局“定义”函数的调用而生成,该函数接受一系列依赖关系(指定为模块名称)以及包含模块主体的回调函数。 全局“定义”功能是通过在应用程序中包含符合AMD要求的加载程序来提供的。 加载程序安排异步加载模块的依赖项,并在完成后调用回调函数,将已解析的模块实例作为自变量按其在依赖项数组中列出的顺序传递为参数。

当为AMD模式编译时,上面的“ main”和“ log”示例生成以下JavaScript代码。

文件main.js:

define(["require", "exports", "./log"], function(require, exports, log_1) {  
    log_1.message("hello");  
}

log.js文件:

define(["require", "exports"], function(require, exports) {  
    function message(s) {  
        console.log(s);  
    }  
    exports.message = message;  
}

始终存在特殊的“需要”和“导出”依赖项。 根据需要,其他条目会添加到依赖项数组和参数列表中,以表示导入的模块。 与CommonJS Modules的代码生成类似,只有在导入模块主体中某处将导入模块引用为PrimaryExpression时,才会为特定的导入模块生成依赖项。 如果仅将导入的模块引用为NamespaceName,则不会为该模块生成依赖关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值