TypeScript 1.5 支持 ECMAScript 6 (ES6) 模块. ES6 模块可以看做之前 TypeScript 的外部模块换上了新的语法: ES6 模块是分开加载的源文件, 这些文件还可能引入其他模块, 并且导出部分供外部可访问. ES6 模块新增了几种导入和导出声明. 我们建议使用 TypeScript 开发的库和应用能够更新到新的语法, 但不做强制要求. 新的 ES6 模块语法和 TypeScript 原来的内部和外部模块结构同时被支持, 如果需要也可以混合使用.
作为 TypeScript 已有的 export
前缀支持, 模块成员也可以使用单独导出的声明导出, 如果需要, as
语句可以指定不同的导出名称.
interface Stream { ... }
function writeToStream(stream: Stream, data: string) { ... }
export { Stream, writeToStream as write }; // writeToStream 导出为 write
引入声明也可以使用 as
语句来指定一个不同的导入名称. 比如:
import { read, write, standardOutput as stdout } from './inout';
var s = read(stdout);
write(stdout, s);
作为单独导入的候选项, 命名空间导入可以导入整个模块:
import * as io from './inout';
var s = io.read(io.standardOutput);
io.write(io.standardOutput, s);
使用 from
语句一个模块可以复制指定模块的导出项到当前模块, 而无需创建本地名称.
export { read, write, standardOutput as stdout } from './inout';
export *
可以用来重新导出另一个模块的所有导出项. 在创建一个聚合了其他几个模块导出项的模块时很方便.
export function transform(s: string): string { ... }
export * from "./mod1";
export * from "./mod2";
一个 export default 声明表示一个表达式是这个模块的默认导出项.
export default class Greeter {
sayHello() {
console.log('Greetings!');
}
}
对应的可以使用默认导入:
import Greeter from './greeter';
var g = new Greeter();
g.sayHello();
"无导入加载" 可以被用来加载某些只需要其副作用的模块.
import './polyfills';
了解更多关于模块的信息, 请参见 ES6 模块支持规范.
TypeScript 1.5 添加了对 ES6 解构声明与赋值的支持.
解构声明会引入一个或多个命名变量, 并且初始化它们的值为对象的属性或者数组的元素对应的值.
比如说, 下面的例子声明了变量 x
, y
和 z
, 并且分别将它们的值初始化为 getSomeObject().x
, getSomeObject().y
和 getSomeObject().z
:
var { x, y, z } = getSomeObject();
解构声明也可以用于从数组中得到值.
var [x, y, z = 10] = getSomeArray();
相似的, 解构可以用在函数的参数声明中:
function drawText({ text = '', location: [x, y] = [0, 0], bold = false }) {
// 画出文本
}
// 以一个对象字面量为参数调用 drawText
var item = { text: 'someText', location: [1, 2, 3], style: 'italics' };
drawText(item);
解构也可以被用于普通的赋值表达式. 举例来讲, 交换两个变量的值可以被写作一个解构赋值:
var x = 1;
var y = 2;
[x, y] = [y, x];
过去 TypeScript 中 module
关键字既可以定义 "内部模块", 也可以定义 "外部模块"; 这让刚刚接触 TypeScript 的开发者有些困惑. "内部模块" 的概念更接近于大部分人眼中的命名空间; 而 "外部模块" 对于 JS 来讲, 现在也就是模块了.
注意: 之前定义内部模块的语法依然被支持.
之前:
module Math {
export function add(x, y) { ... }
}
之后:
namespace Math {
export function add(x, y) { ... }
}
ES6 的 let
和 const
声明现在支持编译到 ES3 和 ES5.
const MAX = 100;
++MAX; // 错误: 自增/减运算符不能用于一个常量
if (true) {
let a = 4;
// 使用变量 a
} else {
let a = 'string';
// 使用变量 a
}
alert(a); // 错误: 变量 a 在当前作用域未定义
TypeScript 1.5 增加了 ES6 for...of
循环编译到 ES3/ES5 时对数组的支持, 以及编译到 ES6 时对满足 Iterator
接口的全面支持.
TypeScript 编译器会转译 for...of
数组到具有语义的 ES3/ES5 JavaScript (如果被设置为编译到这些版本).
for (var v of expr) {
}
会输出为:
for (var _i = 0, _a = expr; _i < _a.length; _i++) {
var v = _a[_i];
}
TypeScript 装饰器是局域 ES7 装饰器 提案的.
一个装饰器是:
- 一个表达式
- 并且值为一个函数
- 接受
target
,name
, 以及属性描述对象作为参数 - 可选返回一个会被应用到目标对象的属性描述对象
了解更多, 请参见 装饰器 提案.
装饰器 readonly
和 enumerable(false)
会在属性 method
添加到类 C
上之前被应用. 这使得装饰器可以修改其实现, 具体到这个例子, 设置了 descriptor
为 writable: false
以及 enumerable: false
.
class C {
@readonly
@enumerable(false)
method() {}
}
function readonly(target, key, descriptor) {
descriptor.writable = false;
}
function enumerable(value) {
return function (target, key, descriptor) {
descriptor.enumerable = value;
};
}
使用动态的属性初始化一个对象可能会很麻烦. 参考下面的例子:
type NeighborMap = { [name: string]: Node };
type Node = { name: string; neighbors: NeighborMap };
function makeNode(name: string, initialNeighbor: Node): Node {
var neighbors: NeighborMap = {};
neighbors[initialNeighbor.name] = initialNeighbor;
return { name: name, neighbors: neighbors };
}
这里我们需要创建一个包含了 neighbor-map 的变量, 便于我们初始化它. 使用 TypeScript 1.5, 我们可以让编译器来干重活:
function makeNode(name: string, initialNeighbor: Node): Node {
return {
name: name,
neighbors: {
[initialNeighbor.name]: initialNeighbor,
},
};
}
作为 AMD
和 CommonJS
模块加载器的补充, TypeScript 现在支持输出为 UMD
(Universal Module Definition) 和 System
模块的格式.
用法:
tsc --module umd
以及
tsc --module system
ES6 中允许用户使用单个转义表示一个 Unicode 码位.
举个例子, 考虑我们需要转义一个包含了字符 '𠮷' 的字符串. 在 UTF-16/USC2 中, '𠮷' 被表示为一个代理对, 意思就是它被编码为一对 16 位值的代码单元, 具体来说是 0xD842
和 0xDFB7
. 之前这意味着你必须将该码位转义为 "\uD842\uDFB7"
. 这样做有一个重要的问题, 就事很难讲两个独立的字符同一个代理对区分开来.
通过 ES6 的码位转义, 你可以在字符串或模板字符串中清晰地通过一个转义表示一个确切的字符: "\u{20bb7}"
. TypeScript 在编译到 ES3/ES5 时会将该字符串输出为 "\uD842\uDFB7"
.
TypeScript 1.4 中, 我们添加了模板字符串编译到所有 ES 版本的支持, 并且支持标签模板字符串编译到 ES6. 得益于 @ivogabe 的大量付出, 我们填补了标签模板字符串对编译到 ES3/ES5 的支持.
当编译到 ES3/ES5 时, 下面的代码:
function oddRawStrings(strs: TemplateStringsArray, n1, n2) {
return strs.raw.filter((raw, index) => index % 2 === 1);
}
oddRawStrings`Hello \n${123} \t ${456}\n world`;
会被输出为:
function oddRawStrings(strs, n1, n2) {
return strs.raw.filter(function (raw, index) {
return index % 2 === 1;
});
}
(_a = ['Hello \n', ' \t ', '\n world']),
(_a.raw = ['Hello \\n', ' \\t ', '\\n world']),
oddRawStrings(_a, 123, 456);
var _a;
/// <amd-dependency path="x" />
会告诉编译器需要被注入到模块 require
方法中的非 TS 模块依赖; 然而在 TS 代码中无法使用这个模块.
新的 amd-dependency name
属性允许为 AMD 依赖传递一个可选的名称.
/// <amd-dependency path="legacy/moduleA" name="moduleA"/>
declare var moduleA: MyType;
moduleA.callStuff();
生成的 JS 代码:
define(['require', 'exports', 'legacy/moduleA'], function (
require,
exports,
moduleA
) {
moduleA.callStuff();
});
通过添加 tsconfig.json
到一个目录指明这是一个 TypeScript 项目的根目录. tsconfig.json
文件指定了根文件以及编译项目需要的编译器选项. 一个项目可以由以下方式编译:
- 调用 tsc 并不指定输入文件, 此时编译器会从当前目录开始往上级目录寻找
tsconfig.json
文件. - 调用 tsc 并不指定输入文件, 使用
-project
(或者-p
) 命令行选项指定包含了tsconfig.json
文件的目录.
{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": true,
"sourceMap": true,
}
}
参见 tsconfig.json wiki 页面 查看更多信息.
选项 --outDir
在输出中会保留输入的层级关系. 编译器将所有输入文件共有的最长路径作为根路径; 并且在输出中应用对应的子层级关系.
有的时候这并不是期望的结果, 比如输入 FolderA\FolderB\1.ts
和 FolderA\FolderB\2.ts
, 输出结构会是 FolderA\FolderB\
对应的结构. 如果输入中新增 FolderA\3.ts
文件, 输出的结构将突然变为 FolderA\
对应的结构.
--rootDir
指定了会输出对应结构的输入目录, 不再通过计算获得.
TypeScript 编译器在需要的时候会输出一些像 __extends
这样的工具函数. 这些函数会在使用它们的所有文件中输出. 如果你想要聚合所有的工具函数到同一个位置, 或者覆盖默认的行为, 使用 --noEmitHelpers
来告知编译器不要输出它们.
默认输出的换行符在 Windows 上是 \r\n
, 在 *nix 上是 \n
. --newLine
命令行标记可以覆盖这个行为, 并指定输出文件中使用的换行符.
--inlineSourceMap
将内嵌源文件映射到 .js
文件, 而不是在单独的 .js.map
文件中. --inlineSources
允许进一步将 .ts
文件内容包含到输出文件中.