迭代器与生成器
涵盖迭代器协议、生成器语法、yield* 委托、async/await 底层关系。
一、标准面试回答(1 分钟)
迭代器是一个定义数据遍历规则的接口,通过 Symbol.iterator 和 next() 方法实现;生成器是 ES6 引入的特殊函数,用 function* 定义,通过 yield 关键字可以暂停和恢复执行,它是迭代器的"语法糖",能更简洁地生成迭代器。
迭代器(Iterator)
是一个对象,必须实现 next() 方法。每次调用 next() 返回一个对象,包含 value(当前值)和 done(是否遍历完成)。JavaScript 中的数组、Map、Set、字符串等内置数据结构都实现了迭代器接口,所以可以用 for...of 遍历。
生成器(Generator)
是返回迭代器的函数,用 function* 声明,内部用 yield 暂停执行。调用生成器函数返回一个迭代器对象,调用 next() 执行到下一个 yield 并返回该表达式的值。生成器可以理解为一个"可暂停的函数"。
核心区别和联系
- 迭代器是接口,定义遍历规则;生成器是实现迭代器的工具函数
- 生成器天然符合迭代器协议,不需要手动维护
next()和状态 - 生成器支持
yield*委托给另一个生成器或可迭代对象
实际应用场景
- 实现自定义遍历:比如树结构、分页数据,用生成器实现深度优先遍历
- 异步流程控制:配合 Promise 实现同步写法(虽然现在被 async/await 取代,但底层原理类似)
- 无限数据结构:生成器可以惰性生成无限序列,比如斐波那契数列
- 状态机:生成器的暂停特性天然适合实现状态机
二、20 秒极简版
迭代器是定义遍历规则的接口,通过 next() 返回 {value, done};生成器是 function* 函数,用 yield 暂停执行,返回迭代器。生成器是迭代器的语法糖,用于自定义遍历、异步控制、无限序列等场景。
三、生成器代码示例
基本生成器
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 }无限序列(惰性生成)
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生成器传值
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*)
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
}递归遍历树形结构
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 值依次传递给当前生成器。
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, 3yield* 相当于将另一个生成器的所有 yield 值展开到当前生成器。也可以委托给数组、字符串等可迭代对象:
function* gen() {
yield* [1, 2, 3] // 依次 yield 1, 2, 3
}应用场景:递归遍历嵌套结构,比如用生成器递归遍历树形结构。
六、追问 3:async/await 和生成器有什么关系?
async/await 本质上是生成器 + Promise + 自动执行器的语法糖。
在 async/await 出现之前,开发者用生成器配合 co 库实现同步风格的异步代码:
co(function* () {
const data = yield fetch('/api')
const result = yield process(data)
console.log(result)
})生成器可以 yield 一个 Promise,执行器在 Promise resolve 后调用 next() 将结果传回,以此实现"暂停等待异步结果"的效果。
async/await 做了三件事:
- 把
function*改成async function - 把
yield改成await - 内置了自动执行器,不需要手动调用
next()
理解这个关系,就能明白为什么 async/await 函数的返回值是 Promise,为什么 await 后面可以是任何 thenable 对象。
七、易错点
- 混淆迭代器和可迭代对象:迭代器是对象(有
next方法),可迭代对象是实现了[Symbol.iterator]方法的对象,返回一个迭代器。两者不是一回事 - 认为生成器函数执行后直接得到值:调用生成器函数返回的是迭代器对象,不是执行结果,必须调用
next()才会执行 yield的返回值记错:next()的参数会作为上一个yield表达式的返回值,这个机制经常被忽略- 说"生成器用于异步编程但现在被 async/await 取代":生成器的用途远不止异步,自定义遍历、惰性序列、状态机等都是独立的应用场景
for...of和for...in的遍历内容混淆:很多人说"for...of遍历键,for...in遍历值",刚好说反。要记住for...in遍历属性名(键),for...of遍历值- 不知道普通对象为什么不能
for...of:说不出"因为对象没有实现[Symbol.iterator]方法"这个根本原因 - 把生成器和普通函数混用:生成器函数不能用
new调用,不能写成箭头函数(function* () {}是标准写法)
八、关联文档
- 05-异步与事件循环(async/await 实现)
- 04-函数大全(生成器基础用法)