Skip to content

迭代器与生成器

作者:青见春山
发表于:2026-07-29
字数统计:4500 字
预计阅读16分钟

涵盖迭代器协议、生成器语法、yield* 委托、async/await 底层关系。

一、标准面试回答(1 分钟)

迭代器是一个定义数据遍历规则的接口,通过 Symbol.iteratornext() 方法实现;生成器是 ES6 引入的特殊函数,用 function* 定义,通过 yield 关键字可以暂停和恢复执行,它是迭代器的"语法糖",能更简洁地生成迭代器

迭代器(Iterator)

是一个对象,必须实现 next() 方法。每次调用 next() 返回一个对象,包含 value(当前值)和 done(是否遍历完成)。JavaScript 中的数组、Map、Set、字符串等内置数据结构都实现了迭代器接口,所以可以用 for...of 遍历。

生成器(Generator)

是返回迭代器的函数,用 function* 声明,内部用 yield 暂停执行。调用生成器函数返回一个迭代器对象,调用 next() 执行到下一个 yield 并返回该表达式的值。生成器可以理解为一个"可暂停的函数"。

核心区别和联系

  • 迭代器是接口,定义遍历规则;生成器是实现迭代器的工具函数
  • 生成器天然符合迭代器协议,不需要手动维护 next() 和状态
  • 生成器支持 yield* 委托给另一个生成器或可迭代对象

实际应用场景

  1. 实现自定义遍历:比如树结构、分页数据,用生成器实现深度优先遍历
  2. 异步流程控制:配合 Promise 实现同步写法(虽然现在被 async/await 取代,但底层原理类似)
  3. 无限数据结构:生成器可以惰性生成无限序列,比如斐波那契数列
  4. 状态机:生成器的暂停特性天然适合实现状态机

二、20 秒极简版

迭代器是定义遍历规则的接口,通过 next() 返回 {value, done};生成器是 function* 函数,用 yield 暂停执行,返回迭代器。生成器是迭代器的语法糖,用于自定义遍历、异步控制、无限序列等场景。

三、生成器代码示例

基本生成器

JavaScript
function* numberGenerator() {
    yield 1
    yield 2
    yield 3
}

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

无限序列(惰性生成)

JavaScript
function* infiniteSequence() {
    let i = 0
    while (true) {
        yield i++
    }
}

const infinite = infiniteSequence()
console.log(infinite.next().value)  // 0
console.log(infinite.next().value)  // 1
console.log(infinite.next().value)  // 2

生成器传值

JavaScript
function* calculator() {
    const a = yield '请输入第一个数字'
    const b = yield '请输入第二个数字'
    return a + b
}

const calc = calculator()
console.log(calc.next())      // { value: '请输入第一个数字', done: false }
console.log(calc.next(5))     // { value: '请输入第二个数字', done: false }
console.log(calc.next(3))     // { value: 8, done: true }

委托生成器(yield*)

JavaScript
function* generateNumbers() {
    yield* [1, 2, 3]
    yield* 'abc'
    yield* [4, 5, 6]
}

for (const value of generateNumbers()) {
    console.log(value)  // 1, 2, 3, a, b, c, 4, 5, 6
}

递归遍历树形结构

JavaScript
function* treeDFS(node) {
    yield node.value
    for (const child of node.children) {
        yield* treeDFS(child)
    }
}

四、追问 1:for...of 和 for...in 的区别?哪些数据结构支持 for...of?

核心区别

for...in:遍历对象的可枚举属性(包括原型链上的),通常用于遍历对象属性,不适合遍历数组(会把数组的额外属性和索引都遍历出来)。

for...of:遍历可迭代对象的 value 值,不遍历原型链,只遍历迭代器返回的数据。它是 ES6 引入的专门用于遍历数据结构的语法。

支持 for...of 的数据结构

  • 数组(Array)
  • 字符串(String)
  • Map、Set
  • arguments 对象
  • NodeList(DOM 元素集合)
  • TypedArray
  • 任何实现了 [Symbol.iterator] 方法的自定义对象

对象(Object)默认不是可迭代的,不能直接 for...of,但可以用 Object.keys()Object.values()Object.entries() 转换成数组再遍历。

五、追问 2:生成器函数中的 yield* 是什么意思?

yield*生成器委托语法,用于在一个生成器中调用另一个可迭代对象(另一个生成器或数组等),相当于把后者的每个 yield 值依次传递给当前生成器。

JavaScript
function* gen1() {
    yield 1
    yield 2
}

function* gen2() {
    yield 0
    yield* gen1()  // 委托给 gen1
    yield 3
}

const it = gen2()
for (const v of it) console.log(v)  // 0, 1, 2, 3

yield* 相当于将另一个生成器的所有 yield 值展开到当前生成器。也可以委托给数组、字符串等可迭代对象:

JavaScript
function* gen() {
    yield* [1, 2, 3]  // 依次 yield 1, 2, 3
}

应用场景:递归遍历嵌套结构,比如用生成器递归遍历树形结构。

六、追问 3:async/await 和生成器有什么关系?

async/await 本质上是生成器 + Promise + 自动执行器的语法糖。

async/await 出现之前,开发者用生成器配合 co 库实现同步风格的异步代码:

JavaScript
co(function* () {
    const data = yield fetch('/api')
    const result = yield process(data)
    console.log(result)
})

生成器可以 yield 一个 Promise,执行器在 Promise resolve 后调用 next() 将结果传回,以此实现"暂停等待异步结果"的效果。

async/await 做了三件事:

  1. function* 改成 async function
  2. yield 改成 await
  3. 内置了自动执行器,不需要手动调用 next()

理解这个关系,就能明白为什么 async/await 函数的返回值是 Promise,为什么 await 后面可以是任何 thenable 对象。

七、易错点

  • 混淆迭代器和可迭代对象:迭代器是对象(有 next 方法),可迭代对象是实现了 [Symbol.iterator] 方法的对象,返回一个迭代器。两者不是一回事
  • 认为生成器函数执行后直接得到值:调用生成器函数返回的是迭代器对象,不是执行结果,必须调用 next() 才会执行
  • yield 的返回值记错next() 的参数会作为上一个 yield 表达式的返回值,这个机制经常被忽略
  • 说"生成器用于异步编程但现在被 async/await 取代":生成器的用途远不止异步,自定义遍历、惰性序列、状态机等都是独立的应用场景
  • for...offor...in 的遍历内容混淆:很多人说"for...of 遍历键,for...in 遍历值",刚好说反。要记住 for...in 遍历属性名(键),for...of 遍历值
  • 不知道普通对象为什么不能 for...of:说不出"因为对象没有实现 [Symbol.iterator] 方法"这个根本原因
  • 把生成器和普通函数混用:生成器函数不能用 new 调用,不能写成箭头函数(function* () {} 是标准写法)

八、关联文档