ES6 - 初学者指南 - 生成器
预告:这个话题很难!我花了一周时间才开始理解。你可能也得读几遍,我花了无数时间看YouTube上的相关视频。最后我会附上我最喜欢的一个视频的链接。
你在说什么?
生成器!ES6 的一项功能,本质上只是一个用于迭代一系列值的函数。然而,它还有一个额外的功能!是什么呢?我听到你问了。好吧,让我试着解释一下,使用生成器时,你可以暂停代码执行来执行某些操作,然后稍后在另一个 clode 块中返回执行。它们一开始可能很吓人,但过一段时间就很容易理解了。对我来说,最难的是找到一个或几个例子,让我稍微了解一下它们为什么有用,以及为什么它们比其他可能的工作方式更好(如果我能找到同时展示两种工作方式的例子就太棒了!)。通常我会先看看 ES5 的工作方式,这次我要稍微改变一下,我们将从 ES6 的方式开始!
那么发电机是什么样子的
function * numberGenerator() {
yield 1
yield 2
yield 3
}
注意*
function 关键字后面的 ,它告诉我们这是一个生成器函数。然后我们有一个新的关键字,这个关键字在函数内部yield
被视为 的迷你版本。return
function * numberGenerator() {
yield 1
yield 2
yield 3
}
const myNumbers = numberGenerator()
当你像上面那样调用一个生成器时,它不会开始执行任何操作,而是处于暂停状态,并返回一个生成器对象。生成器对象内部有 3 个原型,分别称为next()
、return()
和throw()
。我们先来看一下next()
原型。
请输入 next()!
当我们调用下一个原型时,本质上我们所做的就是告诉生成器函数启动并运行,直到遇到 yield 关键字,让我们看一下示例:
function * numberGenerator() {
yield 1
yield 2
yield 3
}
const myNumbers = numberGenerator()
console.log(myNumbers.next()) // This will return { value: 1, done: false } in a console log
这里我们看到我们的代码已经启动并运行到第一个 yield 1。它的输出给我们一个具有 value 属性和 done 属性的对象,done 属性将一直为 false,直到看到最后一个 yield 语句
function * numberGenerator() {
yield 1
yield 2
yield 3
}
const myNumbers = numberGenerator()
console.log(myNumbers.next()) // This will return { value: 1, done: false } in a console log
console.log(myNumbers.next()) // This will return { value: 2, done: false } in a console log
console.log(myNumbers.next()) // This will return { value: 3, done: false } in a console log
console.log(myNumbers.next()) // This will return { value: undefined, done: true } in a console log
上面我们看到,在执行完所有 yield 之后,我们看到了 undefined 的值,而 done 的值为 true。为了使代码执行过程更清晰一些,我们可以在生成器中添加一些日志消息:
function * numberGenerator() {
console.log('Before 1')
yield 1
console.log('Before 2')
yield 2
console.log('Before 3')
yield 3
console.log('After 3')
}
const myNumbers = numberGenerator()
// We will see a console log stating "Before 1"
console.log(myNumbers.next()) // This will return { value: 1, done: false } in a console log
// We will see a console log stating "Before 2"
console.log(myNumbers.next()) // This will return { value: 2, done: false } in a console log
// We will see a console log stating "Before 3"
console.log(myNumbers.next()) // This will return { value: 3, done: false } in a console log
// We will see a console log stating "After 3"
console.log(myNumbers.next()) // This will return { value: undefined, done: true } in a console log
上面的内容让我们更清楚地理解,当我们第一次调用时.next()
,我们将进入我们的函数,并执行直到第一个yield,所以我们将输出Before 1
,然后{ value: 1, done: false}
等等。
那么有些用例怎么样?
在写这篇文章之前,我想尝试找到一些例子来巩固我对这个主题的理解,老实说,我不能说我完全理解它,但我们在这里尝试一下,也许你可以帮助我理解更多的用例?
生成用户 ID
function* generateId() {
let id = 1 // We could take this number from a database lookup
while (true) {
yield id
id++
}
}
const gen = generateId()
console.log(gen.next().value) // This would return 1 in a console log
console.log(gen.next().value) // This would return 2 in a console log
console.log(gen.next().value) // This would return 3 in a console log
在上面的例子中,我们使用 while 循环使生成器成为一个无限循环,始终生成下一个数字。这样做的好处是什么?好吧,如果你尝试while(true)
在自己的代码中运行循环,浏览器会在几秒钟内崩溃,而阻止它的唯一方法就是终止电脑上的浏览器进程(千万不要尝试!),而在生成器中这样做意味着我们一次只能执行一步。
我可以传递参数吗?
是的,你可以将参数传递给生成器的 next() 函数,我必须承认这一点让我困惑了一段时间。简而言之,你可以传入一个参数,但是如果这是第一次调用,.next()
它将不会有任何效果,因为你还没有 yield 任何东西。传递给 next() 的参数.next()
实际上会替换之前 yield 的参数。我将尝试用下面的代码示例来解释:
const maxScore = 5;
function* keepScore() {
let score = 0;
while (true) {
const addToScore = yield score // the first call to .next() will only run to here therefore returning 0
if(addToScore) { // addToScore is populated by the parameter you pass in after the first run
score += addToScore
}
}
}
const playerOne = keepScore()
console.log('score after first move: ')
console.log(playerOne.next()) // This would output 0
console.log('score after second move: ')
console.log(playerOne.next(3)) // This would output 3
console.log('score after third move: ')
console.log(playerOne.next(2)) // This would output 5
console.log('score after fourth move: ')
console.log(playerOne.next()) // This would output 5
console.log('score after fifth move: ')
console.log(playerOne.next(6)) // This would output 11
提前退出
使用生成器可以退出函数,这可以通过两种方式之一实现。首先,你可以使用 call.return()
而不是 next 来退出生成器,或者你可以return
在生成器函数内部使用语句。例如:
const maxCount = 50;
let hitMax = false;
function* countUp() {
let count = 0
while (true) {
const addToCount = yield count
if(addToCount) {
count += addToCount;
}
if(count >= maxCount){
hitMax = true;
return `maxCount has been hit or exceeded`
}
}
}
const counting = countUp();
counting.next();
for(let i=0; !hitMax; i++){
console.log(counting.next(i));
}
console.log("I am done")
上面我们会一直计数,直到hitMax
结果为真,然后停止并退出for
循环,在这个例子中,我们返回到生成器内部。让我们看一个替代方案:
const maxCount = 50;
let hitMax = false;
function* countUp() {
let count = 0
while (true) {
const addToCount = yield count
if(addToCount) {
count += addToCount;
}
if(count >= maxCount){
hitMax = true;
}
}
}
const counting = countUp();
counting.next();
for(let i=0; !counting.next().done; i++){
if(!hitMax){
console.log(counting.next(i));
} else {
console.log(counting.return('maxCount has been hit or exceeded'))
}
}
console.log("I am done")
上面我们必须稍微不同地工作,我们将继续递增,直到done
的值为.next()
真,在循环内我们检查布尔值hitMax
,如果我们命中了它而不是再次计数,我们将调用.return('maxCount has been hit or exceeded')
它将.next().done
值设置为真并允许我们输出“完成”消息。
概述
哇!这是迄今为止我研究过的最难的课题,我想我至少已经基本理解了它。我发现最大的挑战是找到并理解现实世界的用例。我仍然觉得我没有完全理解这个例子,也许你有更好的方法?如果你有,欢迎在评论区分享例子 :) 我从中学到的最重要的一点是:
生成器允许你多次退出并重新进入函数,直到 done 值为 true,这意味着你不需要在生命周期的各个阶段调用多个函数。如果你有更好的解释,请联系我!
12 分钟学会 JavaScript 生成器 - Web 开发简化