前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库自定义迭代器 Symbol.iterator for...of
JavaScript数组与集合

怎么手写 Symbol.iterator,让普通对象支持 for...of 和展开?

给对象定义一个 [Symbol.iterator]() 方法,返回一个有 next() 的对象,next() 每次返回 { value, done }:还有数据时 done 是 false,结束时 done 是 true。想让对象能反复遍历,游标要在每次调用 Symbol.iterator 时新建,不能存在对象本身上。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#数组与集合
先看核心答案读代码示例
理解线索

迭代协议的两层

  1. 可迭代对象有 [Symbol.iterator]() 方法,for...of 和展开语法先调用它拿到迭代器
  2. 迭代器有 next() 方法,自己维护「读到哪了」的游标
  3. return()可选方法,遍历被 break、throw 中断时调用,用来释放资源

正常遍历到结束时不会调用 return(),它只在提前退出时触发。清理逻辑如果两种情况都需要,要在 next() 返回 done: true 时也执行一次。

核心回答

先记住这个答案

普通对象要支持 for...of 和展开,需要实现迭代协议:给对象定义一个 [Symbol.iterator]() 方法,它返回一个迭代器对象;迭代器上有 next() 方法,每次调用返回 { value, done },其中 done 为 false 时 value 是下一项,done 为 true 表示结束。for...of 做的事情就是先调用 [Symbol.iterator]() 拿到迭代器,再反复调用 next(),直到 done 为 true。要让对象能被反复遍历,游标必须在每次调用 [Symbol.iterator]() 时新建,而不是存在对象上共用。遍历中途 break 时,还会调用迭代器可选的 return() 方法,可以在里面释放资源。

  • 可迭代对象有 [Symbol.iterator](),它返回迭代器
  • 迭代器有 next(),每次返回 { value, done }
  • 每次调用 Symbol.iterator 都新建游标,才能反复遍历
  • break 提前退出时会调用迭代器的 return()

手写一个可以反复遍历的范围对象

这里有两个角色要分清:可迭代对象是「能被遍历的东西」,迭代器是「正在遍历的那个游标」。

不用生成器,手写 nextJavaScript
const range = {
  from: 1,
  to: 3,
  [Symbol.iterator]() {
    let cur = this.from // 每次调用都新建一个游标
    const last = this.to
    return {
      next() {
        return cur <= last ? { value: cur++, done: false } : { value: undefined, done: true }
      }
    }
  }
}

console.log(JSON.stringify([...range]))
console.log(JSON.stringify([...range])) // 第二次展开照样有数据
for (const n of range) console.log(n)
查看输出与解释
[1,2,3]
[1,2,3]
1
2
3

cur 是 [Symbol.iterator]() 里的局部变量,每次展开或 for...of 都会重新调用这个方法,得到一个从 1 开始的新游标。所以 range 可以被遍历任意多次,多个 for...of 嵌套遍历同一个 range 也互不干扰。

游标放在对象上的问题

一种常见的偷懒写法是让对象自己既当可迭代对象又当迭代器,[Symbol.iterator]() 直接返回 this,游标存在对象属性上。

只能遍历一次的对象JavaScript
const bad = {
  cur: 1,
  [Symbol.iterator]() { return this }, // 返回自己,游标共用
  next() {
    return this.cur <= 3 ? { value: this.cur++, done: false } : { value: undefined, done: true }
  }
}

console.log(JSON.stringify([...bad]), JSON.stringify([...bad]))
查看输出与解释
[1,2,3] []

第一次展开把 cur 推到了 4,第二次展开拿到的还是同一个对象,cur 已经越过终点,直接结束。这和数组的 values() 读完一次就空了是同一个现象。 返回 this 本身不是错。内建的迭代器,比如 arr.values() 的返回值,它的 [Symbol.iterator]() 就是返回自己,这样迭代器也能直接放进 for...of。问题在于数据容器不该这样写:容器应该每次都给出新的迭代器,迭代器才返回自己。

提前退出时的 return()
迭代器如果持有资源,比如打开的文件、数据库游标、事件订阅,被 break 中断后要能释放。for...of 在 break、return、抛错退出循环时,会调用迭代器的 return() 方法;解构赋值只取前几项时也会调用。正常遍历到 done: true 结束时则不会调用,这时的清理要放在 next() 里做。
回答前,多想一步

容易答错的地方

把 Symbol.iterator 写成字符串键
{ 'Symbol.iterator'() {} } 定义的是一个名叫 "Symbol.iterator" 的普通字符串属性,for...of 不认。必须用计算属性写成 [Symbol.iterator]()。
next 返回的不是对象
next() 必须返回一个对象,如果直接返回值或者 undefined,执行到 for...of 时就会抛 TypeError。结束时也要返回 { done: true },这时 value 可以省略。
以为实现了迭代器就是数组了
对象能被 for...of、展开、Array.from 使用,但它没有 length,也没有 map、filter,用 Array.isArray 判断也是 false。需要数组方法时先 [...obj] 转一下。
试着用自己的话回答

面试官还会怎么问?

用生成器怎么写同样的功能?

把方法写成生成器:*[Symbol.iterator]() { for (let i = this.from; i <= this.to; i++) yield i }。生成器函数每次调用都返回一个新的生成器对象,它天然就是迭代器,也实现了 return(),所以可以反复遍历,break 时生成器里的 finally 块会执行。

能不能写一个无限的迭代器?

可以,next() 永远返回 done: false 就行,比如自增 ID 生成器。用 for...of 遍历时必须自己 break;千万不要展开它或者传给 Array.from,那会一直取下去直到内存耗尽。

异步数据怎么做成可迭代的?

实现 [Symbol.asyncIterator](),它返回的迭代器的 next() 返回 Promise,用 for await...of 遍历。适合分页接口、流式读取这类数据一批批到达的场景,写成异步生成器 async *[Symbol.asyncIterator]() 最简单。

从一道题,走向一组知识

把知识连起来

数组与集合

数组的 keys、values、entries 返回什么?为什么第二次遍历就没数据了?

数组的 keys()、values() 返回的就是内建迭代器,读完一次后同样不会自动重置。

数组与集合

数组展开为什么能复制数组和 Set,它背后到底读取了什么?

展开语法会一直调用 next() 直到 done 为 true,这题的迭代器写好后可以直接拿去展开。

参考资料

  • MDN:迭代器与生成器
  • MDN:迭代协议

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 手写一个可以反复遍历的范围对象
  3. 游标放在对象上的问题
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑