虽然自定义迭代器是一个有用的工具,但由于需要显式地维护其内部状态,因此创建时要格外谨慎。生成器函数(Generator 函数)提供了一个强大的替代选择:它允许你定义一个 非连续执行的函数 作为迭代算法。
生成器函数
最初调用时,生成器函数不执行任何代码,而是返回一种称为 生成器 的特殊迭代器。
function* makeRangeIterator(start = 0, end = Infinity, step = 1) {
let iterationCount = 0;
for (let i = start; i < end; i += step) {
iterationCount++;
yield i;
}
return iterationCount;
}
生成器函数的语法
生成器函数使用 function* 声明编写。在函数内部使用 yield 和 return 控制迭代器的迭代结果。
当调用 next() 方法消耗该生成器时,生成器函数将执行,直至遇到 yield 关键字,生成器函数退出并且生成相应的值。
下次调用 next() 方法时,将从上次 yield 退出的地方继续执行。其上下文(变量绑定)会在重新进入时保存。
在生成器中执行 return 语句会使生成器结束(即返回的对象的 done 属性将被设置为 true)。如果返回一个值,它将被设置为生成器返回的对象的 value 属性。
与 return 语句类似,如果生成器内部抛出错误,生成器也会结束,除非在生成器的代码体内捕获该错误。
当生成器结束后,后续 next() 调用不会执行生成器的任何代码,只会返回一个形如 {value: undefined, done: true} 的对象。
委托给其他生成器或可迭代对象
yield* 表达式用于委托给另一个生成器或可迭代对象。
function* g1() {
yield 2;
yield 3;
yield 4;
}
function* g2() {
yield 1;
yield* g1();
yield 5;
}
var iterator = g2();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: 4, done: false }
console.log(iterator.next()); // { value: 5, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
生成器
生成器函数返回值是一个生成器。
可以根据需要多次调用生成器函数,并且每次都返回一个新的生成器,但每个生成器只能迭代一次。
生成器符合迭代器协议,所以可以将生成器当作迭代器使用:
// it 是一个生成器
let it = makeRangeIterator(1, 10, 2);
let result = it.next();
while (!result.done) {
console.log(result.value); // 1 3 5 7 9
result = it.next();
}
console.log(`已迭代序列的大小:${result.value}`); // 5
生成器是可迭代对象
生成器从它们的 [Symbol.iterator]() 方法中返回 this,所以生成器是可迭代对象。
function* makeIterator() {
yield 1;
yield 2;
}
// it 是生成器
const it = makeIterator();
// 它有一个 [Symbol.iterator]() 方法返回 it(它自己)
console.log(it[Symbol.iterator]() === it); // true
生成器只能迭代一次
虽然生成器是可迭代对象,但是这个可迭代对象只能迭代一次,因为它每次从 [Symbol.iterator]() 返回的迭代器都是同一个。
如果要迭代多次,需要多次调用生成器函数,产生多个生成器。
function* makeIterator() {
yield 1;
yield 2;
}
// it 是生成器
const it = makeIterator();
// 迭代一次 it,会输出 1,2
for (const itItem of it) {
console.log(itItem);
}
// it 对象只能迭代一次,下面的代码什么都不会做
for (const itItem of it) {
console.log(itItem);
}
使用生成器函数自定义的可迭代对象
在可迭代对象 [Symbol.iterator]() 方法处使用生成器函数,那么每次调用时都会返回一个新的生成器(迭代器)。
这种写法避免了手动创建迭代器。
var myIterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
},
};
自定义的可迭代对象可用 for...of 循环或者展开语法进行迭代。
for (let value of myIterable) {
console.log(value);
}
// 1
// 2
// 3
[...myIterable]; // [1, 2, 3]
接收 next() 方法的参数
对于迭代器,在调用 next() 方法时可以传递一个参数。对于使用生成器函数产生的迭代器,也可以接收到这个参数。
只需要在 yield 前面接收参数即可。这样当迭代器调用 next() 方法从 yield 处继续时,传给 next() 方法的参数会赋值给 yield 前面的变量。
传给第一个 next() 的值会被忽略。(因为第一次调用 next() 方法时不是从 yield 处继续)。
function* fibonacci() {
let current = 0;
let next = 1;
while (true) {
const reset = yield current;
[current, next] = [next, next + current];
if (reset) {
current = 0;
next = 1;
}
}
}
const sequence = fibonacci();
console.log(sequence.next().value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2
console.log(sequence.next().value); // 3
console.log(sequence.next().value); // 5
console.log(sequence.next().value); // 8
console.log(sequence.next(true).value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2
相关条目
参考
迭代器和生成器
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Iterators_and_generators
function*
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/function*
Generator
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Generator
yield
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/yield
yield*
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/yield*