ES5/ES3 的生成器(Generators)与迭代(Iteration)
首先,了解一些 ES2016 术语
迭代器(Iterators)
ES2015 引入了 Iterator,它是一个公开了 next、return 和 throw 三个方法的对象,具体接口如下:
tsinterface Iterator<T> {next(value?: any): IteratorResult<T>;return?(value?: any): IteratorResult<T>;throw?(e?: any): IteratorResult<T>;}
这种类型的迭代器对于遍历同步获取的值(例如数组的元素或 Map 的键)非常有用。如果一个对象具有返回 Iterator 对象的 Symbol.iterator 方法,则称该对象是“可迭代的”(iterable)。
迭代器协议还定义了某些 ES2015 特性的目标,例如 for..of、展开运算符(spread operator)以及解构赋值中的数组剩余部分(array rest)。
生成器(Generators)
ES2015 还引入了“生成器”(Generators),它们是可以通过 Iterator 接口和 yield 关键字用于产生部分计算结果的函数。生成器还可以通过 yield * 在内部将调用委托给另一个可迭代对象。例如:
tsfunction* f() {yield 1;yield* [2, 3];}
新的 --downlevelIteration
之前,生成器仅在目标环境为 ES6/ES2015 或更高版本时才受支持。此外,操作迭代器协议的结构(例如 for..of)在 ES6/ES2015 以下的目标版本中,仅在操作数组时才受支持。
TypeScript 2.3 通过 downlevelIteration 标志,为 ES3 和 ES5 目标版本增加了对生成器和迭代器协议的全面支持。
使用 downlevelIteration 时,编译器会使用新的类型检查和编译行为:如果找到 [Symbol.iterator]() 方法,它会尝试在被迭代的对象上调用该方法;如果找不到,则会在该对象上创建一个合成的数组迭代器。
请注意,对于任何非数组值,这需要在运行时具备原生的
Symbol.iterator或Symbol.iterator补丁(shim)。
当使用 downlevelIteration 时,for..of 语句、数组解构以及数组、调用和构造表达式中的展开元素,如果可用,都支持 Symbol.iterator。但即使在运行时或设计时未定义 Symbol.iterator,它们仍然可以用于数组。
异步迭代(Async Iteration)
TypeScript 2.3 增加了对异步迭代器和生成器的支持,如当前的 TC39 提案所述。
异步迭代器(Async iterators)
异步迭代引入了 AsyncIterator,它类似于 Iterator。不同之处在于,AsyncIterator 的 next、return 和 throw 方法返回的是迭代结果的 Promise,而不是结果本身。这允许调用者在 AsyncIterator 推进到产生值的时刻进行异步通知注册。AsyncIterator 具有以下形态:
tsinterface AsyncIterator<T> {next(value?: any): Promise<IteratorResult<T>>;return?(value?: any): Promise<IteratorResult<T>>;throw?(e?: any): Promise<IteratorResult<T>>;}
如果一个对象具有返回 AsyncIterator 对象的 Symbol.asyncIterator 方法,则称该对象是“可异步迭代的”(async-iterable)。
异步生成器(Async Generators)
异步迭代提案引入了“异步生成器”,它们是同样可以用于产生部分计算结果的异步函数。异步生成器还可以通过 yield* 将调用委托给可迭代对象或异步可迭代对象。
tsasync function* g() {yield 1;await sleep(100);yield* [2, 3];yield* (async function*() {await sleep(100);yield 4;})();}
与生成器一样,异步生成器只能是函数声明、函数表达式或类/对象字面量的方法。箭头函数不能作为异步生成器。除了有效的 Symbol.asyncIterator 引用(原生 Symbol 或补丁)外,异步生成器还需要一个有效的全局 Promise 实现(原生实现或与 ES2015 兼容的 polyfill)。
for-await-of 语句
最后,ES2015 引入了 for..of 语句作为遍历可迭代对象的一种方式。同样,异步迭代提案引入了 for..await..of 语句来遍历异步可迭代对象。
tsasync function f() {for await (const x of g()) {console.log(x);}}
for..await..of 语句仅在异步函数或异步生成器内合法。
注意事项
- 请记住,我们对异步迭代器的支持依赖于运行时对
Symbol.asyncIterator的支持。您可能需要对Symbol.asyncIterator进行 polyfill,简单的实现可以是:(Symbol as any).asyncIterator = Symbol.asyncIterator || Symbol.for("Symbol.asyncIterator"); - 如果您还没有
AsyncIterator声明,还需要在lib选项中包含esnext。 - 最后,如果您的目标版本是 ES5 或 ES3,您还需要设置
--downlevelIterators标志。
泛型参数默认值
TypeScript 2.3 增加了对声明泛型类型参数默认值的支持。
示例
考虑一个创建新 HTMLElement 的函数,无参数调用它时生成一个 Div;您还可以选择性地传递子元素列表。以前您必须这样定义它:
tsdeclare 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[]>;
使用泛型参数默认值,我们可以将其简化为:
tsdeclare function create<T extends HTMLElement = HTMLDivElement, U = T[]>(element?: T,children?: U): Container<T, U>;
泛型参数默认值遵循以下规则:
- 如果类型参数具有默认值,则视为可选。
- 必选类型参数不能跟在可选类型参数后面。
- 类型参数的默认类型必须满足该类型参数的约束(如果存在)。
- 指定类型实参时,只需为必选类型参数指定类型实参。未指定的类型参数将解析为它们的默认类型。
- 如果指定了默认类型且推理无法选择候选者,则推断使用该默认类型。
- 与现有类或接口声明合并的类或接口声明,可以为现有的类型参数引入默认值。
- 与现有类或接口声明合并的类或接口声明,可以引入新的类型参数,只要它指定了默认值。
新的 --strict 总控选项
TypeScript 中添加的新检查默认通常是关闭的,以避免破坏现有项目。虽然避免破坏是件好事,但这种策略的缺点是使得选择最高级别的类型安全变得日益复杂,且每次 TypeScript 发布时都需要采取显式的选择加入行动。通过 strict 选项,现在可以实现选择最高级别的类型安全,前提是需要理解随着改进的类型检查特性的添加,编译器可能会报告额外的错误。
新的 strict 编译器选项代表了多项类型检查选项的推荐设置。具体来说,指定 strict 相当于指定了以下所有选项(将来可能会包含更多选项):
精确地说,strict 选项为上述列出的编译器选项设置了默认值。这意味着仍然可以单独控制这些选项。例如:
sh--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 注释来跳过对某些文件的检查;相反,您可以选择通过添加 // @ts-check 注释来仅检查少数几个 .js 文件,而无需设置 checkJs。您还可以通过在特定行前添加 // @ts-ignore 来忽略该行的错误。
.js 文件仍会进行检查以确保它们仅包含标准的 ECMAScript 特性;类型注解仅允许在 .ts 文件中使用,在 .js 文件中会被标记为错误。JSDoc 注释可以用于向 JavaScript 代码添加一些类型信息,有关支持的 JSDoc 结构的详细信息,请参阅 JSDoc 支持文档。
有关更多详细信息,请参阅 类型检查 JavaScript 文件文档。