TypeScript 2.3
面向 ES5/ES3 的生成器和迭代
首先是一些 ES2016 术语:
迭代器
ES2015 引入了 Iterator,它是一个暴露了三个方法的对象,next、return 和 throw,按照以下接口:
interface Iterator<T> {
next(value?: any): IteratorResult<T>;
return?(value?: any): IteratorResult<T>;
throw?(e?: any): IteratorResult<T>;
}这种迭代器对于迭代同步可用的值(例如数组的元素或 Map 的键)很有用。 如果一个对象有一个返回 Iterator 对象的 Symbol.iterator 方法,那么它被认为是“可迭代的”。
迭代器协议还定义了一些 ES2015 特性(如 for..of 和展开运算符以及解构赋值中的数组剩余)的目标。
生成器
ES2015 还引入了“生成器”,它们是可以通过 Iterator 接口和 yield 关键字产生部分计算结果。 生成器还可以通过 yield * 内部委托调用给另一个可迭代对象。例如:
function* f() {
yield 1;
yield* [2, 3];
}新的 --downlevelIteration
以前,只有当目标是 ES6/ES2015 或更高版本时才支持生成器。 此外,对于低于 ES6/ES2015 的目标,只有在操作数组时才支持在迭代器协议上操作的构造(例如 for..of)。
TypeScript 2.3 通过 downlevelIteration 标志,为 ES3 和 ES5 目标添加了对生成器和迭代器协议的全面支持。
使用 downlevelIteration,编译器使用新的类型检查和输出行为,尝试在可迭代对象上调用 [Symbol.iterator]() 方法(如果找到),如果找不到,则为该对象创建一个合成的数组迭代器。
请注意,对于任何非数组值,这需要在运行时有一个原生的
Symbol.iterator或Symbol.iterator的 shim。
当使用 downlevelIteration 时,for..of 语句、数组解构以及数组、调用和 New 表达式中的展开元素在 ES5/E3 中支持 Symbol.iterator(如果可用),但即使数组在运行时或设计时未定义 Symbol.iterator,也可以在其上使用。
异步迭代
TypeScript 2.3 添加了对当前 TC39 提案 中描述的异步迭代器和生成器的支持。
异步迭代器
异步迭代引入了 AsyncIterator,它与 Iterator 类似。 区别在于 AsyncIterator 的 next、return 和 throw 方法返回一个迭代结果的 Promise,而不是结果本身。 这使得调用者能够在 AsyncIterator 前进到产生一个值的时候加入异步通知。 AsyncIterator 具有以下形状:
interface AsyncIterator<T> {
next(value?: any): Promise<IteratorResult<T>>;
return?(value?: any): Promise<IteratorResult<T>>;
throw?(e?: any): Promise<IteratorResult<T>>;
}如果一个对象有一个返回 AsyncIterator 对象的 Symbol.asyncIterator 方法,那么它被认为是“可异步迭代的”。
异步生成器
异步迭代提案引入了“异步生成器”,它们是也可以用于产生部分计算结果的异步函数。 异步生成器还可以通过 yield* 委托调用给一个可迭代对象或异步可迭代对象:
async function* g() {
yield 1;
await sleep(100);
yield* [2, 3];
yield* (async function*() {
await sleep(100);
yield 4;
})();
}与生成器一样,异步生成器只能是函数声明、函数表达式或类或对象字面量的方法。 箭头函数不能是异步生成器。异步生成器需要一个有效的全局 Promise 实现(原生或 ES2015 兼容的 polyfill),此外还需要一个有效的 Symbol.asyncIterator 引用(原生符号或 shim)。
for-await-of 语句
最后,ES2015 引入了 for..of 语句作为迭代可迭代对象的手段。 类似地,异步迭代提案引入了 for..await..of 语句来迭代异步可迭代对象:
async function f() {
for await (const x of g()) {
console.log(x);
}
}for..await..of 语句只在异步函数或异步生成器中合法。
注意事项
- 请记住,我们对异步迭代器的支持依赖于运行时存在
Symbol.asyncIterator。你可能需要 polyfillSymbol.asyncIterator,对于简单用途,可以简单地这样写:(Symbol as any).asyncIterator = Symbol.asyncIterator || Symbol.for("Symbol.asyncIterator"); - 如果尚未拥有
AsyncIterator声明,你还需要在lib选项中包含esnext,以获取该声明。 - 最后,如果你的目标是 ES5 或 ES3,你还需要设置
--downlevelIterators标志。
泛型参数默认值
TypeScript 2.3 增加了对泛型类型参数声明默认值的支持。
示例
考虑一个创建新 HTMLElement 的函数,不带参数调用它生成一个 Div;你也可以选择性地传递一个子元素列表。以前你必须将其定义为:
declare function create(): Container<HTMLDivElement, HTMLDivElement[]>;
declare function create<T extends HTMLElement>(element: T): Container<T, T[]>;
declare function create<T extends HTMLElement, U extends HTMLElement>(
element: T,
children: U[]
): Container<T, U[]>;使用泛型参数默认值,我们可以将其简化为:
declare function create<T extends HTMLElement = HTMLDivElement, U = T[]>(
element?: T,
children?: U
): Container<T, U>;泛型参数默认值遵循以下规则:
- 如果类型参数有默认值,则认为它是可选的。
- 必需的类型参数不能跟在可选的类型参数之后。
- 类型参数的默认类型必须满足该类型参数的约束(如果存在)。
- 当指定类型参数时,你只需要为必需的类型参数指定类型参数。未指定的类型参数将解析为它们的默认类型。
- 如果指定了默认类型且推断无法选择候选,则推断默认类型。
- 与现有类或接口声明合并的类或接口声明可以为现有的类型参数引入默认值。
- 与现有类或接口声明合并的类或接口声明可以引入新的类型参数,只要它指定了默认值。
新的 --strict 主选项
添加到 TypeScript 的新检查通常是默认关闭的,以避免破坏现有项目。 虽然避免破坏是好事,但这种策略的缺点是选择最高级别的类型安全性变得越来越复杂,并且需要在每个 TypeScript 版本上显式选择加入。 通过 strict 选项,可以在理解新版本编译器可能因改进的类型检查功能而报告额外错误的情况下,选择最高级别的类型安全性。
新的 strict 编译器选项代表了一系列类型检查选项的推荐设置。具体来说,指定 strict 相当于指定了以下所有选项(并且未来可能包含更多选项):
确切地说,strict 选项设置了上述编译器选项的默认值。 这意味着仍然可以单独控制这些选项。 例如,
--strict --noImplicitThis false的效果是打开所有严格选项,除了 noImplicitThis 选项。使用这种方案,可以表达由所有严格选项组成但排除某些明确列出的选项的配置。 换句话说,现在可以默认为最高级别的类型安全性,但选择退出某些检查。
从 TypeScript 2.3 开始,由 tsc --init 生成的默认 tsconfig.json 在 "compilerOptions" 部分包含 "strict": true 设置。 因此,使用 tsc --init 启动的新项目将默认启用最高级别的类型安全性。
增强的 --init 输出
除了默认设置 strict 外,tsc --init 的输出也得到了增强。由 tsc --init 生成的默认 tsconfig.json 文件现在包含一组常见的编译器选项,以及它们被注释掉的描述。 只需取消注释你希望设置的配置即可获得所需的行为;我们希望新的输出能够简化新项目的设置,并随着项目的增长保持配置文件的易读性。
在带有 --checkJs 的 .js 文件中报错
默认情况下,TypeScript 编译器不会在 .js 文件中报告任何错误,包括使用 allowJs。 使用 TypeScript 2.3,也可以通过 checkJs 在 .js 文件中报告类型检查错误。
你可以通过向某些文件添加 // @ts-nocheck 注释来跳过检查;相反,你可以选择只检查少数 .js 文件,只需向它们添加 // @ts-check 注释,而不设置 checkJs。 你也可以通过在特定行的前一行添加 // @ts-ignore 来忽略该行的错误。
.js 文件仍然被检查以确保它们只包含标准的 ECMAScript 特性;类型注释只允许在 .ts 文件中,并在 .js 文件中被标记为错误。 JSDoc 注释可用于向 JavaScript 代码添加一些类型信息,有关支持的 JSDoc 结构的更多详细信息,请参见 JSDoc 支持文档。
有关更多详细信息,请参见 JavaScript 文件类型检查文档。