news 2026/1/12 5:35:57

通俗解释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/1/5 0:23:31

基于SpringBoot+Vue的学校防疫物资管理平台管理系统设计与实现【Java+MySQL+MyBatis完整源码】

摘要 新冠疫情暴发以来&#xff0c;学校作为人员密集场所&#xff0c;防疫物资的管理成为保障师生健康安全的重要环节。传统的人工管理方式效率低下&#xff0c;容易出现物资分配不均、库存不足或过期浪费等问题。随着信息化技术的发展&#xff0c;构建一套高效、智能的防疫物资…

作者头像 李华
网站建设 2026/1/9 16:27:25

HardFault_Handler在中断上下文中的行为分析深度剖析

深入HardFault&#xff1a;当它在中断中被触发时&#xff0c;到底发生了什么&#xff1f; 你有没有遇到过这样的场景&#xff1f;系统运行得好好的&#xff0c;突然“啪”一下死机了——LED定格、串口无输出、调试器一连上就停在 HardFault_Handler 。更糟的是&#xff0c;这…

作者头像 李华
网站建设 2026/1/2 1:45:25

YOLOFuse显存占用测试报告:不同融合策略对GPU需求对比

YOLOFuse显存占用测试报告&#xff1a;不同融合策略对GPU需求对比 在智能安防、自动驾驶和夜间监控等现实场景中&#xff0c;单一可见光摄像头在低光照、烟雾或遮挡环境下常常“失明”。此时&#xff0c;红外图像凭借其对热辐射的敏感性&#xff0c;成为补足视觉盲区的关键模态…

作者头像 李华
网站建设 2026/1/2 1:45:02

操作系统概述和硬件视角

操作系统概述和硬件视角 文章目录操作系统概述和硬件视角一、前言二、操作系统的概述2.1 定义2.2 目的2.3 关注点2.4 程序来看OS2.4.1 提出问题2.4.2 解决编译器的很多问题三、硬件视角3.1 组成3.2 核心概念3.2.1 CPU3.2.2 存储器3.2.3 I/O设备3.2.4 总线四、小结一、前言 今天…

作者头像 李华
网站建设 2026/1/2 1:42:36

YOLOFuse轻量化版本开发中:面向嵌入式设备裁剪模型

YOLOFuse轻量化版本开发中&#xff1a;面向嵌入式设备裁剪模型 在智能安防、自动驾驶和工业检测等场景日益复杂的今天&#xff0c;单一视觉模态的局限性正变得越来越明显。尤其是在夜间、烟雾或强光干扰环境下&#xff0c;仅依赖RGB图像的目标检测系统常常“失明”——行人轮廓…

作者头像 李华