WinddSnow

JavaScript-Iterators-and-Generators

字数统计: 2.5k阅读时长: 10 min
2026/07/30

第59课:迭代器与生成器——Symbol.iteratorfor-ofyieldnext()

迭代器(Iterator)和生成器(Generator)是 JavaScript 中处理可遍历数据结构惰性计算的核心机制。ES6 引入了统一的迭代协议——任何实现了 Symbol.iterator 方法的对象都被视为可迭代对象,可以配合 for-of 循环、展开运算符、解构赋值等语法使用。生成器则是一种特殊的函数,可以暂停和恢复执行,每次产出(yield)一个值,是实现自定义迭代器和异步流程控制的强大工具。本节课将系统讲解迭代协议的定义、内置可迭代对象、生成器函数的语法与执行模型,以及两者的实战组合。


1. 迭代协议:可迭代对象与迭代器

迭代协议由两部分组成:

  • 可迭代协议(Iterable Protocol):对象必须实现 Symbol.iterator 方法,该方法返回一个迭代器。
  • 迭代器协议(Iterator Protocol):迭代器必须实现 next() 方法,该方法返回 { value, done } 对象。done: true 表示遍历结束。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const myIterable = {
data: [10, 20, 30],
[Symbol.iterator]() {
let index = 0;
const data = this.data;
return {
next() {
if (index < data.length) {
return { value: data[index++], done: false };
}
return { value: undefined, done: true };
}
};
}
};

for (const item of myIterable) {
console.log(item); // 10, 20, 30
}

通过 for-of 循环,引擎自动调用 Symbol.iterator 获取迭代器,然后不断调用 next() 直到 done === true。这一机制也是展开运算符和数组解构的基础。


2. 内置可迭代对象

JavaScript 提供了多个内置可迭代对象:

类型 示例
字符串 for (const ch of 'hello') {}
数组 for (const item of [1,2,3]) {}
TypedArray Uint8ArrayInt32Array
Map for (const [k, v] of map) {}
Set for (const val of set) {}
arguments for (const arg of arguments) {}(普通函数)
NodeList document.querySelectorAll('div')
1
2
3
4
5
6
7
8
9
10
11
// Map 迭代
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
console.log(key, value); // 'a' 1, 'b' 2
}

// Set 迭代
const set = new Set([1, 2, 2, 3]);
for (const val of set) {
console.log(val); // 1, 2, 3
}

3. for-offor-in 的区别

for-in 遍历的是对象的可枚举属性键(包括原型链上的属性),适用于普通对象,但不应该用于数组(因为它会遍历属性名,且顺序不可靠)。for-of 遍历的是可迭代对象的元素值,不能直接用于普通对象。

1
2
3
4
5
6
7
8
9
10
const arr = [10, 20, 30];
arr.customProp = 'hello';

for (const index in arr) {
console.log(index); // '0', '1', '2', 'customProp' —— 枚举所有可枚举键
}

for (const value of arr) {
console.log(value); // 10, 20, 30 —— 仅遍历迭代器产生的值
}

原则:遍历数组、Map、Set、字符串等,使用 for-of。遍历普通对象属性,使用 for-in 配合 hasOwnProperty 或直接使用 Object.keys/values/entries


4. 生成器函数:function*yield

生成器函数通过 function* 声明(或 function* name),调用时不立即执行函数体,而是返回一个生成器对象(既是迭代器,也是可迭代对象)。

1
2
3
4
5
6
7
8
9
10
11
function* simpleGenerator() {
yield 1;
yield 2;
yield 3;
}

const gen = simpleGenerator();
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 }

yield 表达式暂停函数执行并返回一个值给调用者。调用 next() 恢复执行,直到下一个 yield 或函数结束。yield 表达式的值由下一次 next(arg) 传入的参数决定。

4.1 双向通信:next(arg) 传入值

生成器内部的 yield 表达式可以接收来自外部的值。

1
2
3
4
5
6
7
8
9
10
11
12
function* twoWayGenerator() {
const a = yield 'First yield';
console.log('Received:', a);
const b = yield 'Second yield';
console.log('Received:', b);
return 'Done';
}

const gen = twoWayGenerator();
console.log(gen.next().value); // 'First yield'
console.log(gen.next('Alpha').value); // 'Second yield' (控制台打印 'Received: Alpha')
console.log(gen.next('Beta')); // { value: 'Done', done: true } (控制台打印 'Received: Beta')

第一次 next() 启动生成器,不能传参(传了也会被忽略)。第二次 next('Alpha')'Alpha' 赋值给第一个 yield 表达式的结果 a。第三次 next('Beta')'Beta' 赋给 b

4.2 yield*:委托给另一个可迭代对象

yield* 将生成器内部的控制权委托给另一个可迭代对象(数组、另一个生成器等),逐个产出它的所有值。

1
2
3
4
5
6
7
8
9
10
11
12
13
function* subGenerator() {
yield 'a';
yield 'b';
}

function* mainGenerator() {
yield 1;
yield* subGenerator(); // 委托:产出 'a', 'b'
yield* [2, 3]; // 委托数组
yield 4;
}

console.log([...mainGenerator()]); // [1, 'a', 'b', 2, 3, 4]

yield* 也可以将来自调用者的 next(arg) 参数传递给委托的生成器,实现完全透明的委托。


5. 生成器的高级用法

5.1 生成无限序列(惰性求值)

生成器天然支持惰性求值——仅在需要时才计算下一个值。这适合生成无限序列或数据流。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* fibonacci() {
let [prev, curr] = [0, 1];
while (true) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}

const fib = fibonacci();
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
console.log(fib.next().value); // 5
// 可以无限取下去,但每次只计算一个值

5.2 生成器作为自定义迭代器

任何需要自定义遍历逻辑的对象,都可以使用生成器简化迭代器的实现。

1
2
3
4
5
6
7
8
9
10
11
const range = {
from: 1,
to: 5,
*[Symbol.iterator]() {
for (let i = this.from; i <= this.to; i++) {
yield i;
}
}
};

console.log([...range]); // [1, 2, 3, 4, 5]

使用生成器函数作为 Symbol.iterator,避免了手动维护状态(如 index)和返回 { value, done } 的样板代码。

5.3 异步生成器(简介)

生成器可以与 Promise 结合,用于管理异步流程(常被 async/await 替代)。但在处理异步数据流时,异步生成器(async function*for await...of)提供了优雅的解决方案。异步生成器将在后续深入讲解。


6. 生成器的 return()throw() 方法

生成器对象除了 next() 外,还有两个方法可用于提前终止或注入错误。

  • gen.return(value):立即终止生成器,返回 { value, done: true },生成器内部的 finally 块会执行。
  • gen.throw(error):在生成器内部当前暂停的 yield 位置抛出一个错误,可以在生成器内部 try-catch 捕获。
1
2
3
4
5
6
7
8
9
10
11
12
13
function* canTerminate() {
try {
yield 1;
yield 2;
} finally {
console.log('Cleanup');
}
yield 3;
}

const gen = canTerminate();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.return('early')); // { value: 'early', done: true },控制台打印 'Cleanup'

课后练习

一、概念自测(选择题 / 填空题)

  1. (单选) 一个对象要支持 for-of 循环,它必须实现什么方法?
    A. next()
    B. Symbol.iterator
    C. [Symbol.iterator]()
    D. @@iterator

  2. (单选) 关于生成器函数 function* gen(),以下描述正确的是?
    A. 调用 gen() 立即执行函数体。
    B. 生成器函数内部不能使用 return
    C. 生成器函数返回一个生成器对象,该对象既是迭代器也是可迭代对象。
    D. yield* 只能用于委托另一个生成器。

  3. (填空) 在生成器内部,yield 表达式本身的值是由下一次调用 ______ 时传入的参数决定的。

  4. (多选) 以下哪些类型是内置可迭代对象?
    A. Array
    B. Map
    C. 普通对象 {}
    D. Set

二、AI 编程任务:编写面向 AI 的提示词

场景:你需要编写一个生成器函数 range(start, end, step = 1),生成从 start(包含)到 end(不包含)的等差数列,步长为 step。要求如下:

  • 使用 function* 声明生成器。
  • 如果 step 为正,start 应小于 end,否则不产出任何值;如果 step 为负,start 应大于 end。如果矛盾,生成器立即结束。
  • 生成器内部使用 while 循环和 yield
  • 编写一个使用示例:用 for-of 遍历 range(1, 10, 2) 并打印每个值;再用展开运算符将 range(5, 0, -1) 转为数组并打印。

任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成符合上述要求的 JavaScript 代码。提示词中需明确指定生成器函数的语法、边界条件处理和示例用法。


课后练习答案

一、概念自测答案

  1. C

    • 解析:对象需要实现 [Symbol.iterator]() 方法,返回一个迭代器。Symbol.iterator 是 well-known symbol,通过计算属性名 [Symbol.iterator] 调用。
  2. C

    • 解析:生成器函数返回生成器对象,该对象既实现了迭代器协议(next),又实现了可迭代协议(Symbol.iterator 返回自身)。A 错误,调用 gen() 返回生成器,不立即执行;B 错误,生成器内可使用 return 终止并设置最终 value;D 错误,yield* 可委托任何可迭代对象。
  3. next(arg)next

    • 解析:yield 表达式的值是下一次调用生成器的 next(argument) 时传入的参数。第一次 next() 无法传值。
  4. A、B、D

    • 解析:Array、Map、Set 都是内置可迭代对象。普通对象 {} 默认不是可迭代的,需要手动实现 [Symbol.iterator]() 才能用于 for-of

二、AI 编程任务参考答案(提示词示例)

示例提示词
“请编写一个生成器函数 range(start, end, step = 1),生成等差数列。要求:

  • 使用 function* 声明。
  • 在函数开头检查参数有效性:如果 (step > 0 && start >= end) || (step < 0 && start <= end),则直接 return(生成器结束)。
  • 使用 let current = start; while ((step > 0 && current < end) || (step < 0 && current > end)) { yield current; current += step; } 产出值。
  • 提供一个使用示例:使用 for...of 遍历 range(1, 10, 2) 打印 1,3,5,7,9;使用 console.log([...range(5, 0, -1)]) 打印 [5,4,3,2,1]。
  • 代码简洁,添加 JSDoc 注释。直接输出完整代码。”
CATALOG
  1. 1. 第59课:迭代器与生成器——Symbol.iterator、for-of、yield、next()
    1. 1.1. 1. 迭代协议:可迭代对象与迭代器
    2. 1.2. 2. 内置可迭代对象
    3. 1.3. 3. for-of 与 for-in 的区别
    4. 1.4. 4. 生成器函数:function* 与 yield
      1. 1.4.1. 4.1 双向通信:next(arg) 传入值
      2. 1.4.2. 4.2 yield*:委托给另一个可迭代对象
    5. 1.5. 5. 生成器的高级用法
      1. 1.5.1. 5.1 生成无限序列(惰性求值)
      2. 1.5.2. 5.2 生成器作为自定义迭代器
      3. 1.5.3. 5.3 异步生成器(简介)
    6. 1.6. 6. 生成器的 return() 与 throw() 方法
    7. 1.7. 课后练习
      1. 1.7.1. 一、概念自测(选择题 / 填空题)
      2. 1.7.2. 二、AI 编程任务:编写面向 AI 的提示词
    8. 1.8. 课后练习答案
      1. 1.8.1. 一、概念自测答案
      2. 1.8.2. 二、AI 编程任务参考答案(提示词示例)