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

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

可迭代对象负责开始一次遍历,迭代器负责记住这一次遍历走到了哪里。

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

把对象分成入口与游标两层

  1. 迭代入口Symbol.iterator 创建当前遍历实例
  2. 读取动作next 根据私有索引返回下一项
  3. 结束状态done 为 true 后维持已结束状态

仅有 next 的对象符合迭代器的一部分要求,但不自动成为能被 for...of 接受的可迭代对象;需要对应迭代入口。

核心回答

先记住这个答案

普通对象要支持 for...of 和数组展开,需要提供可调用的 Symbol.iterator 方法,该方法返回带 next 方法的迭代器。next 每次返回对象,done 为 false 时 value 是下一项,done 为 true 表示结束。若希望对象可以重复遍历,应在每次调用 Symbol.iterator 时创建独立游标,而不是把唯一索引放在共享对象上。迭代器自身还可让 Symbol.iterator 返回自身,便于直接继续消费。持有外部资源时要另外考虑提前退出的 return 清理,协议本身不会替业务关闭任意资源。

  • iterable 创建一次遍历,iterator 保存遍历进度
  • next 必须返回符合协议的对象
  • 重复遍历需要独立游标或明确单次消费契约

每次开始遍历都创建新的位置变量

下面把固定数据放在函数闭包里,并在 Symbol.iterator 内声明 index。两个迭代器共享只读数据来源,却有各自的位置,所以一个已经读到第二项不会改变另一个的起点。

返回的迭代器也实现 Symbol.iterator 并返回自身,表示对它继续遍历会接着当前进度走。外层对象则创建新实例,表示重新开始;把这两层区分开就能解释可重复与单次消费。

手写可重复遍历的标签集合JavaScript
function makeLabels() {
  const labels = ['draft', 'ready'];
  return {
    [Symbol.iterator]() {
      let index = 0;
      return {
        next() {
          if (index >= labels.length) return { done: true, value: undefined };
          return { done: false, value: labels[index++] };
        },
        [Symbol.iterator]() { return this; }
      };
    }
  };
}
const labels = makeLabels();
const first = labels[Symbol.iterator]();
const second = labels[Symbol.iterator]();
console.log(first.next().value, first.next().value, second.next().value);
console.log(JSON.stringify([...labels]));
console.log(JSON.stringify([...labels]));
查看输出与解释
draft ready draft
["draft","ready"]
["draft","ready"]

两个游标独立推进,两次展开外层对象都从头读取。若把 index 移到所有迭代器共享的位置,第二次遍历可能为空,这不是 for...of 缓存了错误结果,而是数据源契约改变了。

结束标志与业务值不能混为一谈

undefined 本身可以是合法元素,只要 done 为 false,消费者仍应把它作为这一项处理。不能使用 value 是否为空来判断结束,否则会丢失实际包含 undefined、零或空字符串的序列。

同样,next 返回的必须是对象,直接返回一个数字不符合协议。测试应覆盖空序列、单元素、重复调用结束后的 next 和交错使用两个游标,而不只确认一次展开看起来正常。

有资源的迭代器还要设计关闭路径

循环提前 break 时,协议可以调用迭代器的 return 方法进行收尾;内存固定序列没有外部资源,示例不需要额外关闭逻辑。若封装文件游标或其他资源,应明确结束和提前退出各自怎么释放。

同步迭代器不应把异步读取伪装成已经得到的 value。真正异步来源需要异步迭代协议和对应消费方式,同时定义取消与错误责任;给对象加一个方法名不能消除底层等待。

回答前,多想一步

容易答错的地方

把所有遍历共享的 index 放在外层
如果外层对象宣称可重复遍历,这会让不同消费者互相推进位置,嵌套循环也可能得到意外结果。应每次创建独立游标;若本来只允许消费一次,则需要明确记录这个接口约定。
用 value 为 undefined 代表遍历结束
序列可以合法包含 undefined,结束由 done 表达。应检查完整结果对象并测试空值元素,避免把业务数据和协议控制信息混在同一条件里。
试着用自己的话回答

面试官还会怎么问?

只有 next 方法可以直接展开吗?

不一定,展开需要先取得 Symbol.iterator。可以让迭代器实现这个方法并返回自身,或者用一个外层可迭代对象返回它;next 与迭代入口分别满足不同层次的契约。

生成器会自动解决所有资源问题吗?

生成器简化状态保存,但仍需要在正确位置安排 finally 等清理逻辑,并由消费者正确结束迭代。若外部任务独立启动,结束生成器也不会自动取消所有业务副作用。

为什么不直接返回一个数组?

小型固定数据返回数组通常更简单。自定义迭代适合按需产生结果或提供特定读取顺序,但会增加状态与生命周期复杂度,应根据数据规模和接口需求选择,而非为了使用协议而封装。

从一道题,走向一组知识

把知识连起来

数组与集合

数组 keys、values、entries 返回什么,为什么遍历第二次没有数据?

对照内建数组迭代器的返回内容与消耗行为。

数组与集合

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

理解数组展开怎样持续消费这个协议。

参考资料

  • MDN:迭代器与生成器

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

本题目录
  1. 先记住这个答案
  2. 每次开始遍历都创建新的位置变量
  3. 结束标志与业务值不能混为一谈
  4. 有资源的迭代器还要设计关闭路径
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

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

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