
<br />那么,什么是生成器函数呢?下面,我们先来看一个常规的老式函数。👵🏼<br />

<br />

<br />“但是,为什么你要浪费我5秒钟的时间让我看看这个没劲的普通函数呢?”,这是一个很好的问题。普通函数遵循一种称为运行至完成的模型:当我们调用一个函数时,它将一直运行,直到完成为止(好吧,除非某处出错了)。我们不能随意在中间的某个位置暂停一个函数。<br />
<br />现在最酷的部分来了:生成器函数不遵循运行到完成模型! 🤯这是否意味着我们可以在执行某个生成器函数时随机将其暂停呢?嗯,差不多吧!下面我们看看什么是生成器函数,以及如何使用它们。<br />
<br />我们通过在关键字
function
后加上一个星号*
,来创建一个生成器函数。<br />

- 调用生成器函数会返回一个生成器对象,这个对象是一个迭代器。
- 我们可以在生成器函数中使用
yield
关键字来暂停执行。
<br />但这到底意味着什么?!<br />
<br />我们先看看第一条:调用生成器函数将返回一个生成器对象。当调用常规函数时,函数体将被执行,并最终返回一个值。但是,当调用生成器函数时,会返回生成器对象!下面我们来看看输出生成器函数的返回值时是什么样子。<br />

<br />首先,我们需要退后一小步,并回答常规函数和生成器函数之间的第二个区别:我们可以在生成器函数中用
yield
关键字来“暂停”函数的执行。<br /><br />使用生成器函数,我们可以编写如下代码(
genFunc
是generatorFunction
的缩写):<br />

yield
关键字是做什么的呢?当引擎遇到yield
关键字的时候,生成器的执行将被“暂停”。而且最棒的是,下次运行该函数时,它会记住它先前暂停的位置,然后从那里开始运行!😃 那么,下面基本上就是运行该函数时要发生的事情(不要担心,稍后会对此进行动画演示):<br />
- 第一次运行时,它在第一行“暂停”,并生成字符串值
'✨'
。 - 第二次运行时,它在上一个
yield
关键字的行开始,一直向下运行,直到遇到第二个yield
关键字,并生成值'💕'
。 - 第三次运行时,它在上一个
yield
关键字的行开始,一直向下运行,直到遇到return
关键字,然后返回值'Done!'
。
<br />但是...我们在前面看到的是:如果调用生成器函数,返回的是一个生成器对象,那么如何调用这个生成器函数呢? 🤔 这就是生成器对象发挥作用的地方!<br />
<br />生成器对象(在原型链上)包含一个next()
方法。我们就是用这个方法来迭代生成器对象。但是,为了记住它之前在生成一个值后中断的地方的状态,我们需要将生成器对象赋值给一个变量。我会把这个变量简称为genObj
(generatorObject
的简称)。<br />

genObj
上调用next()
方法时会发生什么!<br />

yield
关键字,而这里这个关键字恰好在第一行!它生成(yield)了一个包含value
属性和done
属性的对象{ value: ... , done: ... }
:<br />
-
value
属性等于我们生成的值。 -
done
属性是一个布尔值,仅在生成器函数返回一个值(不是生成!😊)后,才设置为true
。
<br />我们停止了遍历生成器,这让函数看起来像是暂停了一样!这多酷啊。下面我们再次调用next()
方法!😃<br />

First log!
到控制台中。这条语句既没有yield
也没有return
关键字,所以引擎会继续往下执行!然后,遇到了一个yield
关键字,其值为'💕'
。这样就生成了一个对象,其value
属性的值是'💕'
,done
属性的值为false
,因为我们还没有从生成器中返回。<br /><br />快结束了!下面我们最后一次调用
next()
。<br />

Second log!
被输出到控制台。然后,引擎遇到一个返回值为'Done!'
的return
关键字。引擎返回一个对象,该对象的value
属性值为``'Done!'。这次我们是实际返回了,所以
done属性值被设置为
true!<br /> <br />
done属性实际上非常重要。**我们只能一次迭代一个生成器对象**。什么?!那么当我们再次调用
next()方法时会发生什么呢?<br />[<br />](media/15959149279378/wooo83by4eh12akmg5wb.gif)<br />它只是永远返回
undefined`。如果要再次迭代,必须创建一个新的生成器对象!
如我们所见,生成器函数返回一个迭代器(生成器对象)。但是..等等,迭代器?这是否意味着我们可以在返回的对象上用for...of
循环以及扩展运算符呢?是的!🤩<br />
<br />下面我们试着用[...]
语法,将生成的值扩展到一个数组中。<br />

for...of
循环?!<br />

<br />但是是什么让迭代器成为迭代器呢?我们之所以可以在数组、字符串、map和set上用
for...of
循环和扩展语法,实际上是因为它们实现了迭代器协议:[Symbol.iterator]
。假设我们有以下值(带有很具描述性的名称,哈哈💁🏼♀️):<br />

array
、string
和generatorObject
都是迭代器!下面我们来看看它们的[Symbol.iterator]
属性的值。<br />

[Symbol.iterator]
的值会是什么呢?<br />

[Symbol.iterator]
属性,让不可迭代的值变成可迭代的呢?是的,我们可以!😃<br /><br />
[Symbol.iterator]
必须返回一个迭代器,该迭代器包含一个next()
方法,该方法返回一个对象,这个对象跟我们之前看到过的一样:{ value: '...', done: false/true }
。<br /><br />为简单起见,我们可以简单地将
[Symbol.iterator]
的值设置为等于一个生成器函数,因为这样做默认就返回一个迭代器。下面我们让object
变成可迭代的,并且让生成的值变成一个完整的对象:<br />

object
对象上使用扩展语法或者for...of
循环会发生什么!<br />

Object.keys(this)
而不是this
!<br />


Object.keys(this)
是个数组,所以生成的值也是一个数组。然后我们把这个生成的数组扩展到另一个数组中,结果就得到一个嵌套的数组。我们并不想要这个,只是想生成每个单独的键!<br /><br />好消息!🥳 我们可以用
yield*
关键字在生成器中的迭代器中生成单个值,所以yield
带了一个星号!假设我们有一个生成器函数首先生成一个鳄梨,然后我们想分别生成另一个迭代器的值(在本例中是一个数组)。我们可以用yield*
来做到这一点。然后我们委托给另一个生成器!<br />

genObj
迭代器之前,被委托的生成器的每个值都生成了。<br /><br />这正是为了单独得到所有对象的键所需要做的!<br />

生成器函数的另一种用法,是我们可以(在某种程度上)将它们用作为观察者函数。生成器可以等待传入的数据,并且只有当该数据被传递过来时,才会处理它。比如:<br />

yield [value]
,而是将其赋值给一个称为second
的值,并生成值字符串First!
。这是第一次调用next()
方法时会生成的值。<br /><br />下面我们来看看第一次在可迭代的值上调用
next()
方法时会发生什么。<br />

yield
,并生成值First!
。那么,变量second
的值是什么呢?<br /><br />变量
second
的值实际上是下次我们调用next()
方法时传递给该方法的值!这一次,我们给next()
方法传一个字符串'I like JavaScript'
。<br />

next()
方法的第一次调用没有记录任何输入。我们只是通过第一次调用来启动观察器。生成器在继续之前,等待我们的输入,并可能处理我们传递给next()
方法的值。
那么,为什么我们会想用生成器函数呢?<br />
<br />生成器的最大优点之一就是它们是懒求值的。也就是说,在调用next()
方法后返回的值仅在我们明确要求后才计算!普通函数没有这种功能:所有值都为我们生成了,以防将来某个时间需要用到它。<br />

<br />想像一下,我们有一个读书俱乐部清单!📚为简单起见,每个读书俱乐部只有一个成员。一名会员目前正在读几本书,这些书以数组
books
来表示!<br />

ey812
的书。为找到这本书,我们可能只用一个嵌套的for
循环或者一个forEach
辅助器,但是这意味着我们得遍历所有数据,即使已经找到了要找的俱乐部会员之后也会继续遍历下去。<br /><br />关于生成器,很棒的事情是,除非我们告诉它去运行,否则它不会继续运行。也就是说,我们可以评估每个返回的条目,如果是我们正在找的条目,就不需要调用
next()
了。下面我们看看是啥样子。<br /><br />首先,创建一个生成器,遍历每个会员的
books
数组。我们把会员的books
数组传给该函数,遍历该数组,并生成每本书!<br />

members
)的生成器。我们实际上并不关心俱乐部会员本身,只是需要遍历他们的书。在iterateMembers
生成器中,我们委托iterateBooks
迭代器生成成员们的书!<br />

bookclubs
)。跟上个例子一样,我们实际上并不关心读书俱乐部本身,只关心俱乐部会员(特别是会员们的书)。下面我们委托给iterateMembers
迭代器,并把clubMembers
数组传给它。<br />

bookClubs
数组传递给iterateBookClubs
生成器,让生成器对象可迭代。我现在暂时将其称为生成器对象it
。<br />

next()
方法,直到得到id为ey812
的书。<br />

next()
方法效率不是很高,所以我们来做一个函数吧!<br /><br />下面将我们要找的书的
id
传给该函数。如果value.id
是我们要找的id,那么就只返回整个value
(book对象)。否则,如果不是要找的id
,就再次调用next()
!<br />


next()
方法时候才会生成值!
如果你仍然处于懵逼状态,不要担心。虽然生成器函数确实相当难以理解,但是只要亲自用过它们,并且有一些可靠的案例,那就没那么难了。希望有些术语更清楚了点,跟往常一样:有任务疑问,请随时联系!😃<br />
原文 by Lydia Hallie:https://dev.to/lydiahallie/javascript-visualized-generators-and-iterators-e36
网友评论