35.同步生成器(高级)
原文: http://exploringjs.com/impatient-js/ch_sync-generators.html
35.1。什么是同步生成器?
同步生成器是函数定义和方法定义的特殊版本,它们始终返回同步可迭代:
星号(*
)将函数和方法标记为生成器:
- 功能:伪关键字
function*
是关键字function
和星号的组合。 - 方法:
*
是一个修饰符(类似于static
和get
)。
35.1.1。生成器函数返回 iterables 并通过yield
填充它们
如果调用生成器函数,它将返回一个 iterable(实际上:也是可迭代的迭代器)。生成器通过yield
运算符填充可迭代的:
35.1.2。 yield
暂停生成器功能
到目前为止,yield
看起来像是一种向迭代中添加值的简单方法。但是,它做的远不止于此 - 它还会暂停和退出生成器功能:
- 与
return
类似,yield
退出函数体。 - 与
return
不同,如果再次调用该函数,则会在yield
之后直接执行。
让我们通过以下生成器函数来检查它的含义。
生成器函数的结果称为 生成器对象 。它不仅仅是一个可迭代的,但这超出了本书的范围(如果您对更多细节感兴趣,请参考“探索 ES6”)。
为了使用genFunc2()
,我们必须首先创建生成器对象genObj
。 genFunc2()
现在暂停“身体前”。
genObj
实现迭代协议。因此,我们通过genObj.next()
控制genFunc2()
的执行。调用该方法,恢复暂停的genFunc2()
并执行它直到有yield
。然后执行暂停,.next()
返回yield
的操作数:
请注意,产生的值'a'
包装在一个对象中,这就是迭代总是传递它们的值的方式。
我们再次调用genObj.next()
并继续执行我们先前暂停的位置。一旦遇到第二个yield
,genFunc2()
暂停,.next()
返回产生的值'b'
。
我们再一次调用genObj.next()
并继续执行直到它离开genFunc2()
的主体:
这次,.next()
的结果的属性.done
是true
,这意味着可迭代完成。
35.1.3。为什么yield
暂停执行?
yield
暂停执行有什么好处?为什么它不像 Array 方法.push()
那样工作并用值填充 iterable - 没有暂停?
由于暂停,生成器提供 协同程序 的许多功能(认为协同多任务的进程)。例如,当你要求迭代的下一个值时,该值被计算 懒惰 (按需)。以下两个生成器函数演示了这意味着什么。
请注意,numberLines()
内的yield
出现在for-of
循环内。 yield
可以在循环内部使用,但不能在回调内部使用(稍后会详细介绍)。
让我们结合两个生成器来生成可迭代的numberedLines
:
每次我们通过.next()
向numberedLines
询问另一个值时,numberLines()
只询问genLines()
一行并给它编号。如果genLines()
同步从大文件中读取它的行,我们将能够从文件中读取第一个编号行。如果yield
没有暂停,我们必须等到genLines()
完全读完。
练习:将正常功能转换为发生器
exercises/generators/fib_seq_test.js
35.1.4。示例:迭代迭代
以下函数mapIter()
类似于Array.from()
,但它返回一个可迭代的,而不是一个数组,并根据需要生成其结果。
练习:过滤迭代
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
的实例:
练习:迭代嵌套数组
exercises/generators/iter_nested_arrays_test.js
35.3。示例:重用循环
生成器的一个重要用例是提取和重用循环功能。
35.3.1。要重用的循环
作为一个例子,考虑以下函数迭代文件树并记录它们的路径(它使用 Node.js API 这样做):
我们如何重用这个循环来做除记录路径之外的其他事情?
35.3.2。内部迭代(推送)
重用迭代代码的一种方法是通过 内部迭代 :将每个迭代值传递给回调(A 行)。
35.3.3。外部迭代(拉)
另一种重用迭代代码的方法是通过 外部迭代 :我们可以编写一个生成所有迭代值的生成器。