编译器和编译选项
1、编译器
TypeScript编译器是一段JavaScript程序,能够对TypeScript代码和JavaScript代码进行静态类型检查,并且可以将TypeScript程序编译为可执行的JavaScript程序。TypeScript编译器是自托管编译器[插图],它使用TypeScript语言进行开发。
TypeScript编译器程序位于TypeScript语言安装目录下的lib文件夹中。TypeScript编译器对外提供了一个命令行工具用来编译TypeScript程序,它就是tsc命令。
1.1、安装编译器
安装了Node.js之后,在命令行窗口中运行下列命令来全局安装TypeScript语言:
shell
npm install -g typescript
在不同的操作系统中,全局安装的TypeScript会被安装到不同的目录下。例如,在Windows系统中TypeScript会被安装到"%AppData%\npm"目录下。
1.1.1、--help、--all
在成功地安装了TypeScript后,我们就可以在命令行上使用tsc命令。按照惯例,可以使用"--help"(简写为"-h")选项来显示tsc命令的帮助信息。示例如下:
shell
tsc --help
运行该命令将产生类似于如下的输出结果:
shell
Version 3.8.3
Syntax: tsc [options] [file...]
Examples: tsc hello.ts
tsc --outFile file.js file.ts
tsc @args.txt
tsc --build tsconfig.json
Options:
-h, --help Print this message.
-w, --watch Watch input files.
...
在默认情况下,"--help"选项仅会显示基本的帮助信息。我们可以使用额外的"--all"选项来查看完整的帮助信息。示例如下:
shell
tsc --help --all
1.1.2、--version
使用"--version"命令行选项能够查看当前安装的编译器版本号。示例如下:
shell
tsc --version
Version 3.8.3
2、编译程序
2.1、编译单个文件
TypeScript编译器最基本的使用方式是编译单个文件。假设,当前工程目录结构如下:
C:\app
`-- index.ts
"index.ts"文件的内容如下:
ts
function add(x: number, y: number): number {
return x + y;
}
在"C:\app"目录下运行tsc命令来编译"index.ts"文件,示例如下:
shell
tsc index.ts
默认情况下,编译器会在"C:\app"目录下生成编译后的"index.js"文件,其内容如下:
ts
function add(x, y) {
return x + y;
}
当前工程目录结构如下:
C:\app
|-- index.js
`-- index.ts
如果待编译文件的文件名中带有空白字符,如空格,那么就需要使用转义符号"\"或者使用单、双引号将文件名包围起来。假设当前工程目录结构如下:
C:\app
`-- filename with spaces.ts
在"C:\app"目录下运行tsc命令来编译"filename withspaces.ts"文件。示例如下:
shell
# 使用双引号
tsc "filename with spaces.ts"
# 使用单引号
tsc 'filename with spaces.ts'
# 使用转义符号
tsc filename\ with\ spaces.ts
2.2、编译多个文件
TypeScript编译器能够同时编译多个文件。我们可以在命令行上逐一列出待编译的文件,也可以使用通配符来模糊匹配待编译的文件。
假设当前工程目录结构如下:
C:\app
|-- index.ts
`-- utils.ts
在"C:\app"目录下运行tsc命令来编译"index.ts"和"utils.ts"文件。示例如下:
ts
tsc index.ts utils.ts
除此之外,还可以使用通配符来匹配待编译的文件,支持的通配符包括:
- "*"匹配零个或多个字符,但不包含目录分隔符。
- "?"匹配一个字符,但不包含目录分隔符。
- "**/"匹配任意目录及其子目录。
在"C:\app"目录下运行tsc命令并使用通配符来匹配当前目录下的所有TypeScript文件。示例如下:
shell
tsc *.ts
不论使用以上哪种方式来指定待编译的文件,编译器都会在"C:\app"目录下生成编译后的"index.js"文件和"utils.js"文件,工程目录结构如下:
C:\app
|-- index.js
|-- index.ts
|-- utils.js
`-- utils.ts
2.3、--watch和-w
TypeScript编译器提供了一种特殊的编译模式,即观察模式。在观察模式下,编译器会监视文件的修改并自动重新编译文件。观察模式通过"--watch"(简写为"-w")编译选项来启用。
假设当前工程目录结构如下:
C:\app
`-- index.ts
在"C:\app"目录下运行tsc命令来编译"index.ts"文件并启用观察模式。示例如下:
shell
tsc index.ts --watch
运行tsc命令后,编译器会编译"index.ts"文件并进入观察模式。在命令行窗口中能够看到如下输出消息:
shell
[1:00:00 PM] Starting compilation in watch mode...
[1:00:01 PM] Found 0 errors. Watching for file changes.
这时,如果我们修改"index.ts"文件并保存,编译器会自动重新编译"index.ts"文件。在命令行窗口中能够看到如下输出消息:
shell
[1:00:10 PM] File change detected. Starting incremental compilation...
[1:00:11 PM] Found 0 errors. Watching for file changes.
编译器在重新编译了"index.ts"文件之后依然会继续监视文件的修改。
2.4、--presserveWatchOutput
在观察模式下,编译器每次编译文件之前都会清空命令行窗口中的历史输出信息。如果我们想保留每一次编译的输出信息,则可以使用"--preserveWatchOutput"编译选项。示例如下:
shell
tsc index.ts --watch --preserveWatchOutput
2、编译选项
编译选项是传递给编译器程序的参数,使用编译选项能够改变编译器的默认行为。在编译程序时,编译选项不是必须指定的。
本节不会介绍完整的编译选项列表,而是会列举出部分常用的编译选项并介绍如何使用它们。
2.1、编译选项风格
TypeScript编译选项的命名风格包含以下两种:
- 长名字风格,如"--help"。
- 短名字风格,如"-h"。
每一个编译选项都有一个长名字,但是不一定有短名字。在TypeScript中,不论是长名字风格的编译选项还是短名字风格的编译选项均不区分大小写,即"--help""--HELP""-h""-H"表示相同的含义。
长名字风格的编译选项由两个连字符和一个单词词组构成。提供长名字风格的命令行选项是推荐的做法。它有助于在不同程序之间保持一致的、具有描述性的选项名,从而提高开发者的使用体验。
短名字风格的编译选项名由单个连字符和单个字母构成。TypeScript编译器仅针对一小部分常用的编译选项提供了短名字。如果一个编译选项具有短名字形式,那么该短名字通常为其长名字的首字母,例如"--help"编译选项的短名字为"-h"。
由于提供了短名字的编译选项数量较少且十分常用,因此我们在表中列出了所有支持短名字风格的编译选项。

2.2、使用编译选项
在运行tsc命令时,可以在命令行上指定编译选项。有一些编译选项在使用时不必传入参数,只需要写出编译选项名即可,例如"--version"。示例如下:
shell
tsc --version
我们也可以使用"--version"编译选项的短名字形式"-v"。示例如下:
shell
tsc -v
实际上,每一个编译选项都能够接受一个参数值,只不过有一些编译选项具有默认值,因此也可以省略传入参数。在给编译选项传入参数时,需要将参数写在编译选项名之后,并以空格字符分隔。例如,"--emitBOM"编译选项接受true或false作为参数值。该编译选项设置了编译器在生成输出文件时是否插入byte order mark(BOM)[插图]。示例如下:
shell
tsc --emitBOM true
如果编译选项的参数值是布尔类型并且值为true,那么就可以省略传入参数值。因此,上例中的命令等同于:
shell
tsc --emitBOM
但如果编译选项的参数值不是布尔类型的true或false,那么就不能省略参数值,必须在命令行上设置一个参数值。示例如下:
shell
tsc --target ES5
如果想要同时使用多个编译选项,那么在编译选项之间使用空格分隔即可。示例如下:
shell
tsc --version --locale zh-CN
此例中,同时使用了"--version"和"--locale"编译选项。"--locale"编译选项能够设置显示信息时使用的区域和语言,它的可选值如下:
- 英语:en
- 捷克语:cs
- 德语:de
- 西班牙语:es
- 法语:fr
- 意大利语:it
- 日语:ja
- 韩语:ko
- 波兰语:pl
- 葡萄牙语:pt-BR
- 俄语:ru
- 土耳其语:tr
- 简体中文:zh-CN
2.3、严格类型检查
TypeScript编译器提供了两种类型检查模式,即严格类型检查和非严格类型检查。
非严格类型检查是默认的类型检查模式,该模式下的类型检查比较宽松。在将已有的JavaScript代码迁移到TypeScript时,通常会使用这种类型检查模式,因为这样做可以让迁移工作更加顺利地进行,不至于一时产生过多的错误。
在严格类型检查模式下,编译器会进行额外的类型检查,从而能够更好地保证程序的正确性。严格类型检查功能使用一系列编译选项来开启。在开始一个新的工程时,强烈推荐启用所有严格检查编译选项。对于已有的工程,则可以逐步启用这些编译选项。因为只有如此,才能够最大限度地利用编译器的静态类型检查功能。
2.3.1、--strict
"--strict"编译选项是所有严格类型检查编译选项的"总开关"。如果启用了"--strict"编译选项,那么就相当于同时启用了下列编译选项:
- --noImplicitAny
- --strictNullChecks
- --strictFunctionTypes
- --strictBindCallApply
- --strictPropertyInitialization
- --noImplicitThis
- --alwaysStrict
在实际工程中,我们可以先启用"--strict"编译选项,然后再根据需求禁用不需要的某些严格类型检查编译选项。这样做有一个优点,那就是在TypeScript语言发布新版本时可能会引入新的严格类型检查编译选项,如果启用了"--strict"编译选项,那么就会自动应用新引入的严格类型检查编译选项。
"--strict"编译选项既可以在命令行上使用,也可以在"tsconfig.json"配置文件中使用。在命令行上使用该编译选项,示例如下:
shell
tsc --strict
在"tsconfig.json"配置文件中使用该编译选项,示例如下:
json
{
"compilerOptions": {
"strict": true
}
}
上例的配置等同于如下"tsconfig.json"配置文件:
json
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true
}
}
2.3.2、--nolmplicitAny
若一个表达式没有明确的类型注解并且编译器又无法推断出一个具体的类型时,那么它将被视为any类型。编译器不会对any类型进行类型检查,因此可能存在潜在的错误。
例如,下例中的函数参数str既没有类型注解也无法推断出具体类型,因此它的类型为any类型。不论我们使用哪种类型调用函数f都不会产生编译错误,但如果实际参数不是string类型,那么在代码运行时会产生错误。示例如下:
ts
/**
* --noImplicitAny=false
*/
function f(str) {
// ~~~
// 类型为:any
console.log(str.substring(3));
}
f(42); // 运行时错误
如果启用了"--noImplicitAny"编译选项,那么当表达式的推断类型为any类型时将产生编译错误。因此,上例中的代码在启用了"--noImplicitAny"编译选项的情况下将产生编译错误。示例如下:
ts
/**
* --noImplicitAny=true
*/
function f(str) {
// ~~~
// 编译错误!参数 'str' 隐式地成为 'any' 类型
console.log(str.substring(3));
}
f(42);
2.3.3、--strictNullChecks
若没有启用"--strictNullChecks"编译选项,编译器在类型检查时将忽略undefined值和null值。示例如下:
ts
/**
* --strictNullChecks=false
*/
function f(str: string) {
console.log(str.substring(3));
}
// 以下均没有编译错误,但在运行时产生错误
f(undefined);
f(null);
此例中,函数f期望传入string类型的参数,并且在传入undefined值和null值时,编译器没有产生错误。因为在没有启用"--strictNullChecks"编译选项的情况下,当编译器遇到undefined值和null值时会跳过类型检查。而实际上,此例中的代码在运行时会产生错误,因为在undefined值或null值上调用方法将抛出"TypeError"异常。
如果启用了"--strictNullChecks"编译选项,那么undefined值只能赋值给undefined类型(顶端类型、void类型除外),null值也只能赋值给null类型(顶端类型除外),两者都明确地拥有了各自的类型。因此,上例中的代码在该编译选项下会产生编译错误。示例如下:
ts
/**
* --strictNullChecks=true
*/
function f(str: string) {
console.log(str.substring(3));
}
f(undefined);
//~~~~~~~~~
//编译错误!'undefined' 不能赋值给 'string' 类型的参数
f(null);
//~~~~
//编译错误!'null' 不能赋值给 'string' 类型的参数
2.3.4、--strictFunctionTypes
该编译选项用于配置编译器对函数类型的类型检查规则。
如果启用了"--strictFunctionTypes"编译选项,那么函数参数类型与函数类型之间是逆变关系。
如果禁用了"--strictFunctionTypes"编译选项,那么函数参数类型与函数类型之间是相对宽松的双变关系。
不论是否启用了"--strictFunctionTypes"编译选项,函数返回值类型与函数类型之间始终是协变关系。
2.3.5、--strictBindCallApply
"Function.prototype.call""Function.prototype.bind""Function.prototype.apply"是JavaScript语言中函数对象上的内置方法。这三个方法都能够绑定函数调用时的this值。例如,下例中的6、7、8行将调用函数f时的this值设置成了对象"{ name: 'ts' }":
ts
function f(this: { name: string }, x: number, y: number) {
console.log(this.name);
console.log(x + y);
}
f.apply({ name: 'ts' }, [1, 2]);
f.call({ name: 'ts' }, 1, 2);
f.bind({ name: 'ts' })(1, 2);
如果没有启用"--strictBindCallApply"编译选项,那么编译器不会对以上三个内置方法进行类型检查。虽然函数声明f中定义了this的类型以及参数x和y的类型,但是传入任何类型的实际参数都不会产生编译错误。示例如下:
ts
/**
* --strictBindCallApply=false
*/
function f(this: { name: string }, x: number, y: number) {
console.log(this.name);
console.log(x + y);
}
// 下列语句均没有编译错误
f.apply({}, ['param']);
f.call({}, 'param');
f.bind({})('param');
如果启用了"--strictBindCallApply"编译选项,那么编译器将对以上三个内置方法的this类型以及参数类型进行严格的类型检查。示例如下:
ts
/**
* --strictBindCallApply=true
*/
function f(this: Window, str: string) {
return this.alert(str);
}
f.call(document, 'foo');
// ~~~~~~~~
// 编译错误!'document' 类型的值不能赋值给 'window' 类型的参数
f.call(window, false);
// ~~~~~
// 编译错误!'false' 类型的值不能赋值给 'string' 类型的参数
f.apply(document, ['foo']);
// ~~~~~~~~
// 编译错误!'document' 类型的值不能赋值给 'window' 类型的参数
f.apply(window, [false]);
// ~~~~~
// 编译错误!'false' 类型的值不能赋值给 'string' 类型的参数
f.bind(document);
// ~~~~~~~~
// 编译错误!'document' 类型的值不能赋值给 'window' 类型的参数
// 正确的用法
f.call(window, 'foo');
f.apply(window, ['foo']);
f.bind(window);
2.3.6、--strictPropertyInitialization
该编译选项用于配置编译器对类属性的初始化检查。
如果启用了"--strictPropertyInitialization"编译选项,那么当类的属性没有进行初始化时将产生编译错误。类的属性既可以在声明时直接初始化,例如下例中的属性x,也可以在构造函数中初始化,例如下例中的属性y。如果一个属性没有使用这两种方式之一进行初始化,那么会产生编译错误,例如下例中的属性z。示例如下:
ts
/**
* -- strictPropertyInitialization=true
*/
class Point {
x: number = 0;
y: number;
z: number; // 编译错误!属性 'z' 没有初始值,也没有在构造函数中初始化
constructor() {
this.y = 0;
}
}
若没有启用"--strictPropertyInitialization"编译选项,那么上例中的代码不会产生编译错误。也就是说,允许未初始化的属性z存在。
使用该编译选项时需要注意一种特殊情况,有时候我们会在构造函数中调用其他方法来初始化类的属性,而不是在构造函数中直接进行初始化。目前,编译器无法识别出这种情况,依旧会认为类的属性没有被初始化,进而产生编译错误。我们可以使用"!"类型断言来解决这个问题,示例如下:
ts
/**
* -- strictPropertyInitialization=true
*/
class Point {
x: number; // 编译错误:属性 'x' 没有初始值,也没有在构造函数中初始化
y!: number; // 正确
constructor() {
this.initX();
this.initY();
}
private initX() {
this.x = 0;
}
private initY() {
this.y = 0;
}
}
2.3.7、--noImplicitThis
与"--noImplicitAny"编译选项类似,在启用了"--noImplicitThis"编译选项时,如果程序中的this值隐式地获得了any类型,那么将产生编译错误。示例如下:
ts
/**
* -- noImplicitThis=true
*/
class Rectangle {
width: number;
height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
getAreaFunctionWrong() {
return function () {
return this.width * this.height;
// ~~~~ ~~~~
// 编译错误:'this' 隐式地获得了 'any' 类型
// 因为不存在类型注解
};
}
getAreaFunctionCorrect() {
return function (this: Rectangle) {
return this.width * this.height;
};
}
}
2.3.8、--alwaysStrict
ECMAScript 5引入了一个称为严格模式[插图]的新特性。在全局JavaScript代码或函数代码的开始处添加""usestrict""指令就能够启用JavaScript严格模式。在模块和类中则会始终启用JavaScript严格模式。注意,JavaScript严格模式不是本节所讲的TypeScript严格类型检查模式。
在JavaScript严格模式下,JavaScript有着更加严格的语法要求和一些新的语义。例如,implements、interface、let、package、private、protected、public、static和yield都成了保留关键字;在函数的形式参数列表中,不允许出现同名的形式参数等。
若启用了"--alwaysStrict"编译选项,则编译器总是以JavaScript严格模式的要求来检查代码,并且在编译生成JavaScript代码时会在代码的开始位置添加""use strict""指令。示例如下:
ts
/**
* --alwaysStrict=true
*/
function outer() {
if (true) {
function inner() {
// ~~~~~
// 编译错误!当编译目标为'ES3'或'ES5'时,
// 在严格模式下的语句块中不允许使用函数声明
}
}
}
此例中,只有在启用了"--alwaysStrict"编译选项时,第6行代码才会产生编译错误。因为在JavaScript严格模式下,语句块中不允许出现函数声明。
2.4、编译选项列表
随着TypeScript版本的更新,提供的编译选项列表也会有所变化。例如,一些编译选项会被废弃,也会有一些新加入的编译选项。推荐读者到TypeScript官方网站上的"Compiler Options"页面[插图]中了解最新的编译选项列表。
官方网站的Compiler Options页面地址:https://www.typescriptlang.org/docs/handbook/compiler-options.html。