Generator
常规函数只会返回一个单一值(或者不返回任何值)。
而 Generator 可以按需一个接一个地返回(“yield”)多个值。它们可与 iterable
完美配合使用,从而可以轻松地创建数据流。
Generator 函数
要创建一个 generator,我们需要一个特殊的语法结构:function*
,即所谓的 “generator function”。
它看起来像这样:
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
Generator 函数与常规函数的行为不同。在此类函数被调用时,它不会运行其代码。而是返回一个被称为 “generator object” 的特殊对象,来管理执行流程。
我们来看一个例子:
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
// "generator function" 创建了一个 "generator object"
let generator = generateSequence();
alert(generator); // [object Generator]
到目前为止,上面这段代码中的 函数体 代码还没有开始执行:
一个 generator 的主要方法就是 next()
。当被调用时(译注:指 next()
方法),它会恢复上图所示的运行,执行直到最近的 yield <value>
语句(value
可以被省略,默认为 undefined
)。然后函数执行暂停,并将产出的(yielded)值返回到外部代码。
next()
的结果始终是一个具有两个属性的对象:
value
: 产出的(yielded)的值。done
: 如果 generator 函数已执行完成则为true
,否则为false
。
例如,我们可以创建一个 generator 并获取其第一个产出的(yielded)值:
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
let one = generator.next();
alert(JSON.stringify(one)); // {value: 1, done: false}
截至目前,我们只获得了第一个值,现在函数执行处在第二行:
让我们再次调用 generator.next()
。代码恢复执行并返回下一个 yield
的值:
let two = generator.next();
alert(JSON.stringify(two)); // {value: 2, done: false}
如果我们第三次调用 generator.next()
,代码将会执行到 return
语句,此时就完成这个函数的执行:
let three = generator.next();
alert(JSON.stringify(three)); // {value: 3, done: true}
现在 generator 执行完成。我们通过 done:true
可以看出来这一点,并且将 value:3
处理为最终结果。
再对 generator.next()
进行新的调用不再有任何意义。如果我们这样做,它将返回相同的对象:{done: true}
。
function* f(…)
或function *f(…)
?这两种语法都是对的。
但是通常更倾向于第一种语法,因为星号
*
表示它是一个 generator 函数,它描述的是函数种类而不是名称,因此*
应该和function
关键字紧贴一起。
Generator 是可迭代的
当你看到 next()
方法,或许你已经猜到了 generator 是可迭代(iterable)的。(译注:next()
是 iterator 的必要方法)
我们可以使用 for..of
循环遍历它所有的值:
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
for(let value of generator) {
alert(value); // 1,然后是 2
}
for..of
写法是不是看起来比 .next().value
优雅多了?
……但是请注意:上面这个例子会先显示 1
,然后是 2
,然后就没了。它不会显示 3
!
这是因为当 done: true
时,for..of
循环会忽略最后一个 value
。因此,如果我们想要通过 for..of
循环显示所有的结果,我们必须使用 yield
返回它们:
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
let generator = generateSequence();
for(let value of generator) {
alert(value); // 1,然后是 2,然后是 3
}
因为 generator 是可迭代的,我们可以使用 iterator 的所有相关功能,例如:spread 语法 ...
:
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
let sequence = [0, ...generateSequence()];
alert(sequence); // 0, 1, 2, 3
在上面这段代码中,...generateSequence()
将可迭代的 generator 对象转换为了一个数组
使用 generator 进行迭代
在Iterable object(可迭代对象) 中,我们可以创建一个可迭代的 range
对象,它返回 from..to
的值。
let range = {
from: 1,
to: 5,
// for..of range 在一开始就调用一次这个方法
[Symbol.iterator]() {
// ...它返回 iterator object:
// 后续的操作中,for..of 将只针对这个对象,并使用 next() 向它请求下一个值
return {
current: this.from,
last: this.to,
// for..of 循环在每次迭代时都会调用 next()
next() {
// 它应该以对象 {done:.., value :...} 的形式返回值
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
}
};
// 迭代整个 range 对象,返回从 `range.from` 到 `range.to` 范围的所有数字
alert([...range]); // 1,2,3,4,5
我们可以通过提供一个 generator 函数作为 Symbol.iterator
,来使用 generator 进行迭代:
下面是一个相同的 range
,但紧凑得多:
let range = {
from: 1,
to: 5,
*[Symbol.iterator]() { // [Symbol.iterator]: function*() 的简写形式
for(let value = this.from; value <= this.to; value++) {
yield value;
}
}
};
alert( [...range] ); // 1,2,3,4,5
之所以代码正常工作,是因为 range[Symbol.iterator]()
现在返回一个 generator,而 generator 方法正是 for..of
所期望的:
- 它具有
.next()
方法 - 它以
{value: ..., done: true/false}
的形式返回值
当然,这不是巧合。Generator 被添加到 JavaScript 语言中是有对 iterator 的考量的,以便更容易地实现 iterator。
带有 generator 的变体比原来的 range
迭代代码简洁得多,并且保持了相同的功能。
Generator 可以永远产出(yield)值
在上面的示例中,我们生成了有限序列,但是我们也可以创建一个生成无限序列的 generator,它可以一直产出(yield)值。例如,无序的伪随机数序列。
这种情况下肯定需要在 generator 的
for..of
循环中添加一个break
(或者return
)。否则循环将永远重复下去并挂起。