news 2026/6/10 11:45:34

通俗解释ES6:Iterator遍历器的核心概念

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
通俗解释ES6:Iterator遍历器的核心概念

从“如何遍历”到“能被遍历”:深入理解 ES6 中的 Iterator 机制

你有没有想过,为什么 JavaScript 中的数组可以用for...of遍历,字符串也可以,连SetMap甚至函数内的arguments都能轻松地被展开或循环?而你自己写的一个对象却不行?

const arr = [1, 2, 3]; for (const item of arr) { console.log(item); // 正常输出 1, 2, 3 } const obj = { a: 1, b: 2 }; // for (const item of obj) // 报错!obj is not iterable

这背后其实有一套统一的规则在起作用——这就是ES6 引入的 Iterator(遍历器)机制。它不是某个具体的数据结构,而是一种“行为协议”,决定了一个东西“能不能被遍历”以及“怎么被遍历”。


一、问题驱动:为什么需要 Iterator?

在 ES6 之前,JavaScript 的遍历方式五花八门:

  • 数组用for (let i = 0; i < arr.length; i++)
  • 对象属性用for...in
  • 类数组对象(如arguments)还得借用Array.prototype.forEach.call()
  • 自定义结构?抱歉,得自己实现一套逻辑

这些方式各有各的语义和限制,缺乏统一性。更麻烦的是,如果你想让别人用熟悉的语法来操作你的数据类型(比如一棵树、一个懒加载队列),就必须手动模拟整个流程。

于是,ES6 提出了一个核心思想:

把“遍历的能力”从数据结构中剥离出来,变成一种可复用的标准接口。

这个接口就是Iterator 模式


二、Iterator 是什么?一个简单的协议

我们先不谈复杂概念,来看最本质的一点:Iterator 其实就是一个有next()方法的对象

每次调用next(),返回一个形如{ value: ..., done: boolean }的结果。

  • value:当前拿到的值;
  • done:是否已经遍历完了。

就这么简单。

动手实现一个计数器迭代器

function createCounter(max) { let current = 0; return { next() { if (current < max) { return { value: current++, done: false }; } else { return { value: undefined, done: true }; } } }; }

使用它:

const counter = createCounter(3); console.log(counter.next()); // { value: 0, done: false } console.log(counter.next()); // { value: 1, done: false } console.log(counter.next()); // { value: 2, done: false } console.log(counter.next()); // { value: undefined, done: true }

看到没?这个对象就是一个标准的iterator。它符合规范,能一步步往外“吐”值,直到结束。

但注意:你现在还不能对它使用for...of—— 因为它只是个 iterator,还不是“可迭代对象”。


三、可迭代协议:让对象“能被遍历”

刚才那个counter虽然本身是 iterator,但它不能直接放进for...of里用。因为 JS 不知道“怎么开始遍历它”。

要解决这个问题,就需要另一个协议:可迭代协议(Iterable Protocol)

它的规则只有一条:

如果一个对象有一个方法,名叫Symbol.iterator,并且这个方法返回一个 iterator,那它就是“可迭代的”。

也就是说,“可迭代对象” ≠ “迭代器”,而是“能生成迭代器的东西”。

让 Range 成为可迭代对象

举个例子,我们做一个范围类Range(1, 5),表示从 1 到 4 的整数序列。

class Range { constructor(start, end) { this.start = start; this.end = end; } [Symbol.iterator]() { let current = this.start; const end = this.end; return { next() { if (current < end) { return { value: current++, done: false }; } else { return { value: undefined, done: true }; } } }; } }

现在试试看:

const range = new Range(1, 5); for (const n of range) { console.log(n); // 输出 1, 2, 3, 4 } const arr = [...range]; // [1, 2, 3, 4]

成功了!Range现在可以像数组一样被for...of和扩展运算符使用。

关键就在于这一行:

[Symbol.iterator]() { ... }

这是 JS 的“魔法入口”。当你写for...of someObj时,引擎会自动查找someObj[Symbol.iterator]()并调用它,拿到 iterator 后再反复执行next()


四、Generator:让创建迭代器变得极其简单

上面的例子虽然清晰,但代码有点啰嗦。尤其是状态管理(current变量)容易出错。

ES6 还提供了一个更强大的工具:Generator 函数

它本质上是一个“能暂停的函数”,配合yield使用,天然返回一个符合 Iterator 接口的对象。

用 Generator 改写 ID 生成器

function* idGenerator() { let id = 1; while (true) { yield id++; } }

调用它:

const gen = idGenerator(); gen.next(); // { value: 1, done: false } gen.next(); // { value: 2, done: false }

而且它自己就实现了Symbol.iterator

gen[Symbol.iterator]() === gen; // true → 它自己就是可迭代的

所以可以直接用于for...of

for (const id of idGenerator()) { if (id > 5) break; console.log('ID:', id); // 输出 1 到 5 }

是不是简洁太多了?不需要手动维护next()和状态判断,yield就像一个“产出点”,每走到这里就暂停并返回值。


五、实际应用场景:Iterator 不只是用来循环

别以为 Iterator 只是为了替代for循环。它的真正威力在于抽象遍历过程,适用于很多高级场景。

场景 1:遍历树结构(中序遍历)

假设你有一棵二叉树,想让用户用for...of直接遍历所有节点值。

class TreeNode { constructor(value, left = null, right = null) { this.value = value; this.left = left; this.right = right; } *[Symbol.iterator]() { if (this.left) yield* this.left; yield this.value; if (this.right) yield* this.right; } }

解释一下:

  • yield*表示“委托给另一个可迭代对象”;
  • 左子树如果是TreeNode,也实现了[Symbol.iterator],就能递归展开;
  • 最终效果是按中序顺序依次产出每个节点的值。

使用起来就像普通数组一样自然:

const root = new TreeNode( 2, new TreeNode(1), new TreeNode(3) ); for (const val of root) { console.log(val); // 输出 1, 2, 3 }

完全隐藏了内部结构的复杂性。


场景 2:处理大数据流或懒加载资源

想象你要读取一个超大文件,或者分页获取远程数据。如果一次性加载进内存,可能会崩溃。

这时可以用 Iterator 实现“按需加载”:

function* paginatedUsers(pageSize = 10) { let page = 1; while (true) { const response = await fetch(`/api/users?page=${page}&size=${pageSize}`); const users = await response.json(); if (users.length === 0) break; for (const user of users) { yield user; // 每次只返回一个用户 } page++; } }

等等……这不行!Generator 函数内部不能用await

别急,ES2018 提供了异步迭代器(Async Iterator),支持for await...of

改写如下:

async function* asyncPaginatedUsers(pageSize = 10) { let page = 1; while (true) { const response = await fetch(`/api/users?page=${page}&size=${pageSize}`); const users = await response.json(); if (users.length === 0) break; for (const user of users) { yield user; } page++; } }

然后这样使用:

for await (const user of asyncPaginatedUsers()) { console.log(user.name); }

这就是现代前端处理无限滚动、日志流、WebSocket 数据的基础模型之一。


六、设计建议与常见坑点

✅ 好实践

  1. 每次[Symbol.iterator]()应返回新实例
    js [Symbol.iterator]() { let current = this.start; return { /* 返回新的 next 函数 */ }; }
    避免多个遍历共享状态导致混乱。

  2. 无限迭代器记得加退出条件
    js for (const x of infiniteGen()) { if (x > 100) break; // 必须手动中断 }

  3. 优先使用 Generator 创建 iterator
    写法更清晰,不易出错,还能结合try...catch处理异常。


❌ 常见误区

错误做法问题说明
在普通对象上直接加next()它不是可迭代对象,无法用于for...of
Symbol.iterator返回固定对象多次遍历时状态共享,第二次可能为空
Generator 中混用yieldreturn不当return会提前终结迭代

七、总结:Iterator 到底带来了什么?

回到最初的问题:Iterator 的意义是什么?

我们可以这样理解:

以前现在
数据结构决定怎么遍历遍历方式由协议决定
每种结构各搞一套 API所有结构共用一套语法
用户关心“怎么取下一个”用户只关心“拿到下一个”

Iterator 把“如何遍历”的细节封装起来,对外暴露统一的消费接口。

它是现代 JavaScript 中许多特性的基石:

  • for...of
  • 扩展运算符...
  • 解构赋值[a, b, ...rest] = iterable
  • Array.from(),Promise.all(iterable)
  • yield*
  • for await...of
  • Redux-Saga、RxJS 等库的核心模型

掌握 Iterator,不只是学会一种语法,更是理解 JS 如何通过协议 + 语法糖构建出强大而灵活的抽象能力。当你下次想封装一个自定义集合、实现懒加载、或是设计一个状态机时,不妨问一句:

“这个东西,能让别人用for...of来遍历吗?”

如果答案是肯定的,那你已经走在写出更优雅代码的路上了。

如果你在项目中用过 Iterator 解决实际问题,欢迎在评论区分享你的经验!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/9 23:33:24

YOLOFuse训练自定义数据集完整步骤:目录结构+配置修改+启动命令

YOLOFuse训练自定义数据集完整实践指南 在智能安防、夜间巡检和应急救援等现实场景中&#xff0c;单一可见光摄像头常常“看不清”——低光照、烟雾遮挡或伪装目标让传统目标检测模型频频失效。而红外图像能捕捉热辐射信息&#xff0c;在黑暗环境中依然清晰成像。于是&#xff…

作者头像 李华
网站建设 2026/6/10 13:33:58

YOLOFuse能否用于军事用途?开源协议中的限制条款

YOLOFuse能否用于军事用途&#xff1f;开源协议中的限制条款 在人工智能加速渗透现实世界的今天&#xff0c;一个看似普通的开源项目可能悄然具备改变应用场景边界的潜力。YOLOFuse 就是这样一个典型例子——它最初只是为了解决夜间监控中“看不清”的问题而诞生的多模态目标检…

作者头像 李华
网站建设 2026/6/10 14:55:41

circuit simulator全面讲解:傅里叶分析在谐波检测中的应用

电路仿真中的谐波检测实战&#xff1a;用傅里叶分析“听清”畸变信号的真相你有没有遇到过这样的情况&#xff1f;设计了一个看似完美的电源电路&#xff0c;结果实测输出电压总是“毛刺不断”&#xff0c;噪声频谱像一团乱麻&#xff1b;或者音频放大器明明参数达标&#xff0…

作者头像 李华
网站建设 2026/6/10 14:24:11

基于Ultralytics YOLO的多模态目标检测镜像上线,支持特征级与决策级融合

基于Ultralytics YOLO的多模态目标检测镜像上线&#xff0c;支持特征级与决策级融合 在城市安防监控中心的大屏前&#xff0c;值班人员正盯着夜间园区的实时画面——可见光摄像头几乎一片漆黑&#xff0c;而红外图像虽能捕捉热源&#xff0c;却难以分辨物体类别。传统单模态模…

作者头像 李华
网站建设 2026/6/9 20:40:32

YOLOFuse工业质检新思路:高温部件红外异常识别

YOLOFuse工业质检新思路&#xff1a;高温部件红外异常识别 在钢铁厂的连铸车间&#xff0c;通红的金属坯料正缓缓移动&#xff0c;周围弥漫着热浪与烟雾。传统视觉系统因强光反射和环境干扰频频“失明”&#xff0c;而此时一台双模相机却清晰捕捉到了表面细微裂纹引发的局部温度…

作者头像 李华
网站建设 2026/6/10 14:15:25

YOLOFuse快递分拣中心监控:包裹破损识别与追责

YOLOFuse快递分拣中心监控&#xff1a;包裹破损识别与追责 在快递行业高速运转的今天&#xff0c;一个包裹从揽收到送达往往要经过多个分拣中心。每一次传送带的转动、每一次机械臂的抓取&#xff0c;都可能对包裹造成潜在损伤。而当客户投诉“收到破损件”时&#xff0c;运营方…

作者头像 李华