35.同步生成器(高级)

原文: http://exploringjs.com/impatient-js/ch_sync-generators.html

35.1。什么是同步生成器?

同步生成器是函数定义和方法定义的特殊版本,它们始终返回同步可迭代:

星号(*)将函数和方法标记为生成器:

  • 功能:伪关键字function*是关键字function和星号的组合。
  • 方法:*是一个修饰符(类似于staticget)。

35.1.1。生成器函数返回 iterables 并通过yield填充它们

如果调用生成器函数,它将返回一个 iterable(实际上:也是可迭代的迭代器)。生成器通过yield运算符填充可迭代的:

35.1.2。 yield暂停生成器功能

到目前为止,yield看起来像是一种向迭代中添加值的简单方法。但是,它做的远不止于此 - 它还会暂停和退出生成器功能:

  • return类似,yield退出函数体。
  • return不同,如果再次调用该函数,则会在yield之后直接执行。

让我们通过以下生成器函数来检查它的含义。

生成器函数的结果称为 生成器对象 。它不仅仅是一个可迭代的,但这超出了本书的范围(如果您对更多细节感兴趣,请参考“探索 ES6”)。

为了使用genFunc2(),我们必须首先创建生成器对象genObjgenFunc2()现在暂停“身体前”。

genObj实现迭代协议。因此,我们通过genObj.next()控制genFunc2()的执行。调用该方法,恢复暂停的genFunc2()并执行它直到有yield。然后执行暂停,.next()返回yield的操作数:

请注意,产生的值'a'包装在一个对象中,这就是迭代总是传递它们的值的方式。

我们再次调用genObj.next()并继续执行我们先前暂停的位置。一旦遇到第二个yieldgenFunc2()暂停,.next()返回产生的值'b'

我们再一次调用genObj.next()并继续执行直到它离开genFunc2()的主体:

这次,.next()的结果的属性.donetrue,这意味着可迭代完成。

35.1.3。为什么yield暂停执行?

yield暂停执行有什么好处?为什么它不像 Array 方法.push()那样工作并用值填充 iterable - 没有暂停?

由于暂停,生成器提供 协同程序 的许多功能(认为协同多任务的进程)。例如,当你要求迭代的下一个值时,该值被计算 懒惰 (按需)。以下两个生成器函数演示了这意味着什么。

请注意,numberLines()内的yield出现在for-of循环内。 yield可以在循环内部使用,但不能在回调内部使用(稍后会详细介绍)。

让我们结合两个生成器来生成可迭代的numberedLines

每次我们通过.next()numberedLines询问另一个值时,numberLines()只询问genLines()一行并给它编号。如果genLines()同步从大文件中读取它的行,我们将能够从文件中读取第一个编号行。如果yield没有暂停,我们必须等到genLines()完全读完。

35.同步生成器(高级) - 图1 练习:将正常功能转换为发生器

exercises/generators/fib_seq_test.js

35.1.4。示例:迭代迭代

以下函数mapIter()类似于Array.from(),但它返回一个可迭代的,而不是一个数组,并根据需要生成其结果。

35.同步生成器(高级) - 图2 练习:过滤迭代

exercises/generators/filter_iter_gen_test.js

35.2。从生成器调用生成器(高级)

35.2.1。通过yield*调用生成器

yield只能直接在生成器中工作 - 到目前为止,我们还没有看到将屈服委托给另一个函数或方法的方法。

让我们首先检查 不是 的工作原理:在下面的例子中,我们希望foo()调用bar(),以便后者为前者产生两个值。唉,天真的方法失败了:

为什么这不起作用?函数调用bar()返回一个我们忽略的 iterable。

我们想要的是foo()产生bar()产生的所有东西。这就是yield*运算符的作用:

换句话说,之前的foo()大致相当于:

请注意,yield*适用于任何可迭代:

35.2.2。示例:在树上迭代

yield*允许我们在生成器中进行递归调用,这在迭代递归数据结构(如树)时很有用。举例来说,二叉树的数据结构如下。

方法[Symbol.iterator]()增加了对迭代协议的支持,这意味着我们可以使用for-of循环迭代BinaryTree的实例:

35.同步生成器(高级) - 图3 练习:迭代嵌套数组

exercises/generators/iter_nested_arrays_test.js

35.3。示例:重用循环

生成器的一个重要用例是提取和重用循环功能。

35.3.1。要重用的循环

作为一个例子,考虑以下函数迭代文件树并记录它们的路径(它使用 Node.js API 这样做):

我们如何重用这个循环来做除记录路径之外的其他事情?

35.3.2。内部迭代(推送)

重用迭代代码的一种方法是通过 内部迭代 :将每个迭代值传递给回调(A 行)。

35.3.3。外部迭代(拉)

另一种重用迭代代码的方法是通过 外部迭代 :我们可以编写一个生成所有迭代值的生成器。

35.4。生成器的高级功能

  • yield也可以通过.next() 接收 数据。有关详细信息,请参阅“探索 ES6”
  • yield*的结果是其操作数返回的结果。有关详细信息,请参阅“探索 ES6”