JavaScript迭代与生成器全解|for...in/for...of、Symbol.iterator、可迭代对象、yield高级用法、生成器双向通信

迭代与生成器真正重要的不是会不会写 function*,而是能不能说清楚统一迭代协议、惰性取值和 next 传参为什么能把数据流变成可暂停、可恢复的过程。

迭代(Iterator)与生成器(Generator)是 ES6 高阶核心语法,是 JS 实现统一遍历协议、异步流程控制、惰性求值的底层支撑。绝大多数开发者只会简单使用 for 循环,却不懂遍历底层原理、迭代协议、生成器双向通信,这也是中高级前端、大厂面试高频难点。

本文全覆盖指定知识点:for...of vs for...in 核心差异、Symbol.iterator 迭代协议、原生可迭代对象、yield 高级用法、生成器双向通信,从零吃透迭代体系底层逻辑。

一、遍历与迭代的核心区别

很多人混淆遍历和迭代,二者底层逻辑完全不同:

  • 遍历:单纯逐个获取集合元素,无统一规范(for、forEach、for...in),不同数据结构遍历规则不统一

  • 迭代:基于统一迭代协议,通过迭代器对象逐个产出数据,惰性取值,可控、可暂停、可终止

核心结论for...in 是遍历for...of 是迭代(遵循迭代协议)。

二、for...in vs for...of(面试必考对比)

两种循环看似都是遍历,底层机制、适用场景、坑点完全不同,是前端基础高频面试题。

2.1 for...in(遍历键名)

专门用于遍历对象可枚举属性键名,不推荐遍历数组!

核心特性

  • 遍历自身+原型链上的可枚举属性

  • 遍历数组时,索引为字符串类型,非数字

  • 遍历顺序不保证有序

  • 无法遍历 Symbol 类型属性

const arr = [10, 20, 30]
arr.name = '数组'

// 遍历索引(字符串)+ 自定义属性
for(const key in arr) {
  console.log(key) // '0'、'1'、'2'、'name'
}

2.2 for...of(迭代值)

ES6 全新迭代循环,遵循迭代协议,只能遍历可迭代对象,是数组、集合、类数组遍历首选。

核心特性

  • 直接遍历,而非键名

  • 不遍历原型属性、不遍历自定义可枚举属性

  • 支持 break、continue、return(可中断)

  • 支持遍历 Symbol 属性

  • 自动抹平数组空位(empty → undefined)

const arr = [10, 20, 30]
for(const item of arr) {
  console.log(item) // 10、20、30
}

2.3 终极对比总结

特性 for...in for...of
遍历内容 键名/索引 元素值
遍历原型属性 ✅ 会遍历 ❌ 不遍历
中断循环 ✅ 支持 ✅ 支持
适用场景 纯普通对象遍历 数组、Map、Set、类数组
迭代协议 不遵循 遵循 Symbol.iterator 协议

三、Symbol.iterator 迭代协议(底层核心)

可迭代对象的本质:只要对象部署了 Symbol\.iterator 方法,就是可迭代对象,可被 for...of、扩展运算符、Array.from、解构遍历。

3.1 迭代器运行机制

  1. 调用对象的 \[Symbol\.iterator\]\(\)方法,生成迭代器对象

  2. 迭代器拥有唯一核心方法 next\(\)

  3. 每次调用 next() 返回结构:\{ value: 当前值, done: 是否遍历完毕 \}

  4. done 为 true 时,迭代结束

3.2 手动实现迭代器

const arr = [1,2,3]
// 获取迭代器
const iterator = arr[Symbol.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 } 迭代结束

3.3 自定义可迭代对象(面试手写)

普通对象默认不可迭代,手动部署迭代协议,即可被 for...of 遍历:

const obj = {
  data: [10, 20, 30],
  // 部署迭代协议
  [Symbol.iterator]() {
    let index = 0
    const data = this.data
    return {
      next() {
        return index < data.length 
          ? { value: data[index++], done: false }
          : { value: undefined, done: true }
      }
    }
  }
}

// 可正常迭代
for(const item of obj) {
  console.log(item) // 10、20、30
}

四、原生可迭代对象大全

以下原生数据结构默认部署 Symbol.iterator,天然支持 for...of 迭代:

  • Array 数组:最常用可迭代对象

  • String 字符串:逐字符迭代

  • Map 集合:迭代键值对,有序

  • Set 集合:迭代元素值,去重有序

  • arguments:函数内置类数组可迭代对象

  • NodeList/HTMLCollection:DOM 查询结果集合

4.1 特殊可迭代对象遍历示例

// Map 迭代
const map = new Map([['name','张三'],['age',20]])
for(const [key, val] of map) {
  console.log(key, val)
}

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

// arguments 迭代
function fn() {
  for(const arg of arguments) {
    console.log(arg)
  }
}
fn(100,200)

五、Generator 生成器基础

生成器函数:ES6 提供的可暂停、可恢复特殊函数,是迭代器的语法糖,自带迭代协议,专门用于实现惰性迭代、异步流程控制。

5.1 核心特征

  • 函数声明带 function\* 标识

  • 内部通过 yield 暂停代码执行

  • 调用生成器函数,不执行代码,仅返回迭代器对象

  • 通过 next\(\) 恢复执行,分段产出值

5.2 基础语法

function* gen() {
  yield 1
  yield 2
  yield 3
  return '结束'
}

const g = gen()
console.log(g.next()) // {value:1, done:false}
console.log(g.next()) // {value:2, done:false}
console.log(g.next()) // {value:3, done:false}
console.log(g.next()) // {value:'结束', done:true}

六、yield 高级用法

yield 不只是单纯暂停,具备表达式运算、值接收、嵌套迭代等高级能力,是生成器核心难点。

6.1 yield 表达式返回值

重点:yield 产出的值是 next().value,yield 本身的返回值是下次 next 传入的参数

function* gen() {
  // res 接收第二次 next 传入的参数
  const res = yield 100
  console.log(res) // 200
  yield 200
}
const g = gen()
g.next()        // 产出100
g.next(200)     // 给上一次 yield 赋值,产出200

6.2 yield* 委托迭代(嵌套迭代)

yield\* 用于委托迭代其他可迭代对象/生成器,简化嵌套迭代写法。

function* inner() {
  yield 'a'
  yield 'b'
}

function* outer() {
  yield 1
  yield* inner() // 委托迭代内部生成器
  yield 2
}

// 迭代结果:1 a b 2
for(const item of outer()) console.log(item)

七、生成器双向通信(顶级难点)

普通迭代是单向取值,生成器支持双向通信:外部可以向生成器内部传递数据、抛出异常、强制终止,实现内外数据交互,是 Generator 控制异步流程的核心原理。

7.1 正向通信:next() 传参

外部通过 next\(val\) 向生成器内部传入值,赋值给上一次 yield 表达式,实现数据从外部流向内部。

function* gen() {
  console.log('启动')
  let num1 = yield '等待参数1'
  console.log('接收参数1:', num1)
  
  let num2 = yield '等待参数2'
  console.log('接收参数2:', num2)
}

const g = gen()
console.log(g.next())         // 启动、{value: '等待参数1', done: false}
console.log(g.next(666))      // 接收参数1:666、{value: '等待参数2', done: false}
console.log(g.next(888))      // 接收参数2:888、{value: undefined, done: true}

注意:第一次 next() 传参无效,没有上一次 yield 接收参数。

7.2 异常通信:throw() 抛错

外部可通过 throw\(\) 向生成器内部抛出错误,内部通过 try/catch 捕获,实现异常双向处理。

function* gen() {
  try {
    yield 1
    yield 2
  } catch (err) {
    console.log('内部捕获错误:', err)
  }
}

const g = gen()
g.next()
// 外部抛错,内部捕获
g.throw('程序异常!')

7.3 强制终止:return() 结束迭代

return\(val\) 强制终止生成器迭代,直接修改 done 为 true,后续 yield 不再执行。

function* gen() {
  yield 1
  yield 2
  yield 3
}
const g = gen()
console.log(g.next())     // {value:1, done:false}
console.log(g.return(999))// {value:999, done:true} 强制结束
console.log(g.next())     // {value:undefined, done:true}

7.4 双向通信核心价值

  • 实现分步异步传参,替代嵌套回调

  • 精准控制异步流程的暂停、恢复、终止、异常捕获

  • 是 async/await 的底层实现原理(Generator + Promise 自动化执行)

八、迭代与生成器工程实战场景

  • 惰性求值:大数据分步迭代,避免一次性加载阻塞内存

  • 异步流程控制:顺序执行多个异步请求,替代回调地狱

  • 无限序列生成:生成无限递增数字、随机数等序列

  • 数据分片遍历:长列表、大文件分片迭代处理

  • 状态机实现:通过 yield 暂停切换不同状态

九、全文核心总结(面试必背)

  • for...in:遍历键名、遍历原型属性、不可迭代协议,仅用于普通对象

  • for...of:遍历值、遵循迭代协议、可中断、不遍历原型属性,是遍历集合首选

  • Symbol.iterator:迭代协议核心,部署该方法即为可迭代对象

  • 原生可迭代对象:Array、String、Map、Set、arguments、NodeList

  • yield:暂停生成器执行,支持表达式传值,yield* 可委托嵌套迭代

  • 生成器双向通信:next传参、throw抛错、return终止,实现内外数据交互,是异步终极方案底层

十、高频面试简答题

  • for...in 和 for...of 的区别? for...in 遍历键名、遍历原型属性、不可中断(可中断但不推荐)、无迭代协议;for...of 遍历值、遵循迭代协议、可中断、不遍历原型属性,适配所有可迭代对象。

  • **什么是可迭代对象?**部署了 Symbol.iterator 方法,可生成迭代器、被 for...of/扩展运算符遍历的对象。

  • yield 和 yield* 的区别? yield 用于暂停单次执行、产出单个值;yield* 用于委托迭代其他可迭代对象,实现嵌套迭代。

  • 生成器双向通信原理? 通过 next() 向内部传参、throw() 内部抛错、return() 终止迭代,实现内外双向数据交互与流程控制。

  • Generator 和 async/await 的关系? async/await 是 Generator + Promise 的语法糖,解决了 Generator 需要手动 next() 的痛点,实现异步自动执行。

本文总结

  • for...in 和 for...of 的差异本质上是“遍历键名”和“基于迭代协议产出值”的差异,不只是语法选择问题。
  • Symbol.iterator 定义了对象能否进入统一迭代体系,而 Generator 则把手写迭代器的样板代码压缩成更可控的语法糖。
  • yield 表达式、yield* 和 next 传参一起构成了生成器的双向通信能力,这才是它在异步流程控制里真正有价值的地方。
GYSTACK 文章文末广告 硅云云服务器活动 适合个人项目、轻量建站和出海业务部署。
后浪云移动端信息流广告 后浪云主机服务 适合长期部署、独立站和海外机房需求。