悠悠楠杉
JavaScript生成器函数与迭代器详解
在现代JavaScript开发中,异步编程和数据流处理变得越来越重要。为了更优雅地管理复杂的控制流程,ES6引入了两个强大的语言特性:生成器函数(Generator Functions) 和 迭代器(Iterators)。它们不仅为开发者提供了更灵活的数据遍历方式,还为异步操作的同步化书写奠定了基础。
什么是迭代器?
在JavaScript中,迭代器是一个符合迭代器协议的对象,即它拥有一个 next() 方法,该方法返回一个形如 { value: any, done: boolean } 的对象。其中 value 表示当前迭代的值,done 是一个布尔值,表示是否已经遍历完成。
原生支持迭代器的数据结构包括数组、字符串、Map、Set等。这些对象之所以能被 for...of 循环遍历,是因为它们实现了 可迭代协议 —— 即拥有一个以 Symbol.iterator 为键的方法。
javascript
const arr = [1, 2, 3];
const iterator = arrSymbol.iterator;
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: undefined, done: true }
通过手动调用 next(),我们可以逐步获取每一个值。这种“惰性求值”的机制使得处理大量数据或无限序列成为可能。
生成器函数:一种特殊的函数
生成器函数是定义迭代器的简便方式。它使用 function* 语法声明,并通过 yield 关键字暂停和恢复执行。
javascript
function* countUp() {
yield 1;
yield 2;
yield 3;
}
const gen = countUp();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
每次调用 next(),函数会从上次 yield 暂停的地方继续执行,直到遇到下一个 yield 或函数结束。这使得生成器可以按需产生值,避免一次性计算所有结果。
yield 与 return 的区别
yield 不仅可以返回值,还能接收外部传入的值。当 next(value) 被调用时,传入的参数会作为当前 yield 表达式的返回值。
javascript
function* echo() {
const input = yield "ready";
yield You said: ${input};
}
const g = echo();
console.log(g.next().value); // "ready"
console.log(g.next("Hello").value); // "You said: Hello"
而 return 会终结生成器,使后续调用 next() 都返回 { done: true }。
实际应用场景
生成器非常适合处理无限序列或大数据流。例如,实现一个斐波那契数列生成器:
javascript
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
for (let i = 0; i < 10; i++) {
console.log(fib.next().value);
}
这段代码不会导致内存溢出,因为数值是按需生成的。
此外,生成器曾被广泛用于异步流程控制(如 co 库),虽然现在已被 async/await 取代,但其思想依然影响深远。async 函数本质上是返回 Promise 的生成器的语法糖。
自定义可迭代对象
结合生成器和 Symbol.iterator,我们可以轻松创建自定义可迭代类型:
javascript
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}
*Symbol.iterator {
for (let i = this.start; i <= this.end; i++) {
yield i;
}
}
}
for (const n of new Range(1, 5)) {
console.log(n); // 1, 2, 3, 4, 5
}
这种方式让我们的类也能被 for...of、扩展运算符等语法自然支持。
