news 2026/3/26 13:15:06

迭代器协议在ES6中的实现方式:手把手教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
迭代器协议在ES6中的实现方式:手把手教程

深入理解 ES6 迭代器:从协议原理到实战应用

你有没有遇到过这样的场景?想遍历一个数据结构,却发现它不支持for...of;或者想封装一个无限序列,又担心内存爆炸。这些问题背后,其实都指向 JavaScript 中一个强大但常被忽视的底层机制 ——迭代器协议

在 ES5 时代,我们靠for(i=0; i<arr.length; i++)for...in打天下。但这些方式各自为政、规则混乱:数组用下标,对象遍历还可能带上原型链上的属性,顺序还不保证。直到 ES6 引入了统一的迭代器与可迭代协议,才真正让“遍历”这件事变得标准、安全且可扩展。

今天我们就来彻底搞懂这套机制 —— 不只是“怎么用”,更要明白“为什么这样设计”。从最基础的协议定义,到手写斐波那契生成器,再到生成器函数的优雅替代方案,一步步带你打通 JS 遍历系统的任督二脉。


什么是迭代器协议?别被术语吓到

先抛开那些复杂的名词。想象你在吃一盒巧克力,每次只能拿一颗,吃完最后一颗就停止。这个过程本质上就是“迭代”:一次取一个,直到结束。

JavaScript 的迭代器协议正是对这种行为的抽象。它不关心数据存在哪儿(数组?链表?网络流?),只规定两点:

  1. 调用.next()方法;
  2. 返回{ value: 当前值, done: 是否结束 }

只要满足这个约定,任何对象都可以称为迭代器

而如果一个对象能通过[Symbol.iterator]()方法返回这样一个迭代器,那它就是可迭代对象—— 就像那盒巧克力有个“请取下一块”的按钮。

// 举个真实例子 const arr = [1, 2, 3]; const it = arr[Symbol.iterator](); it.next(); // { value: 1, done: false } it.next(); // { value: 2, done: false } it.next(); // { value: 3, done: false } it.next(); // { value: undefined, done: true }

看到没?for...of看似魔法,底层其实就是不断调用next()直到donetrue


哪些东西天生就能被 for…of 遍历?

不是所有对象都能直接上for...of的餐桌。下面这张表帮你一眼看清常见类型的“可迭代性”:

类型可迭代?示例
Array[1,2,3]
String'hi''h','i'
Map键值对逐一返回
Set每个唯一值依次输出
arguments函数内可用
NodeListDOM 查询结果如querySelectorAll
普通 Object{a:1}不能直接for...of

🔥 关键点:普通对象默认没有实现Symbol.iterator,所以不能直接用于for...of。你可以手动加,也可以用Object.keys(obj)转成数组再遍历。

这也解释了为什么这段代码会报错:

for (const item of { a: 1, b: 2 }) { ... } // TypeError: is not iterable

因为它根本没有提供“如何一步步取出数据”的方法。


动手实现一个可迭代对象:斐波那契数列生成器

理论讲完,现在来点硬货 —— 我们自己造一个能无限生成斐波那契数的类,并让它支持for...of

第一步:做一个“只会吐数字”的迭代器

我们要的是一个能记住状态、每次返回下一个斐波那契数的对象:

class FibonacciIterator { constructor() { this.prev = 0; this.curr = 1; } next() { const value = this.curr; const nextValue = this.prev + this.curr; this.prev = this.curr; this.curr = nextValue; return { value, done: false }; // 先假设永远不停 } }

注意这里的done: false—— 因为我们做的是无限序列,永远不会主动终止。

第二步:让它变成“可迭代”的

现在的问题是:FibonacciIterator自己是个迭代器,但它本身不是“可迭代对象”。要让for...of能用,必须有个地方放Symbol.iterator

所以我们需要一个包装类:

class Fibonacci { [Symbol.iterator]() { return new FibonacciIterator(); } }

就这么简单![Symbol.iterator]是一个方法,调用时返回一个新的迭代器实例。每次你开始for...of,都会拿到一个全新的、从头开始的状态。

第三步:试试看能不能跑起来

const fib = new Fibonacci(); let count = 0; for (const num of fib) { console.log(num); if (++count >= 5) break; } // 输出:1, 1, 2, 3, 5

成功了!而且你会发现,这完全是惰性计算的 —— 只有当你真的去取值的时候,才会算出下一个数。这对处理大数据或实时流非常友好。

⚠️ 注意:因为是无限循环,一定要加break,否则主线程会被卡死。


Symbol.iterator 到底是谁?为什么非得用它?

你可能会问:为什么偏偏是Symbol.iterator?不能叫iterable()或者别的名字吗?

答案是:为了防止命名冲突

ES6 引入了Symbol类型,就是为了创建全局唯一的标识符。Symbol.iterator是语言内置的一个“知名符号”(well-known symbol),专门用来标记“这个对象可以被遍历”。

当引擎看到for (x of obj)时,它做的第一件事就是:

const iteratorFn = obj[Symbol.iterator]; if (typeof iteratorFn === 'function') { const it = iteratorFn.call(obj); // 开始调用 it.next() } else { throw new TypeError('is not iterable'); }

所以如果你忘了写Symbol.iterator方法,或者写成了字符串'Symbol.iterator',都会导致失败。

常见坑点提醒

❌ 忘记写done属性
next() { return { value: 42 }; // 缺少 done,默认视为 false }

后果:无限输出 42,直到页面崩溃。

✅ 正确做法:

next() { return { value: 42, done: true }; }
❌ 把Symbol.iterator写成普通字段
{ [Symbol.iterator]: {} // 不是函数! }

引擎会尝试调用它,结果报错:“not a function”。

✅ 记住:它必须是一个无参函数,返回迭代器。


实际开发中更推荐的做法:用生成器函数

上面我们手写了完整的类和方法,虽然有助于理解原理,但在实际项目中显得太啰嗦了。好在 ES6 还给了我们一把利器 ——生成器函数

一行代码搞定斐波那契

function* fibonacci() { let [prev, curr] = [0, 1]; while (true) { yield curr; [prev, curr] = [curr, prev + curr]; } }

就这么几行,就已经自动满足迭代器协议了!

const fib = fibonacci(); for (const n of fib) { console.log(n); if (n > 100) break; }

你甚至不需要手动实现next()Symbol.iterator—— 生成器函数返回的对象天然具备这些能力。

为什么说生成器更优雅?

  • 自动管理状态:变量保留在函数作用域里,不用挂在this上;
  • 语法清晰yield表达式直观表达了“这里暂停并返回”;
  • 天然合规:返回的对象自带next()Symbol.iterator
  • 支持委托:可以用yield*复用其他生成器逻辑。

比如你想合并两个序列:

function* combined() { yield* fibonacci(); yield* range(1, 5); // 假设有个 range 生成器 }

干净利落。


迭代器在现代前端架构中的位置

别以为这只是语法糖。实际上,迭代器协议已经渗透到了现代 JavaScript 的方方面面,成为许多高级特性的基石。

它支撑着哪些核心语法?

语法底层依赖
for...of✅ 必须可迭代
扩展运算符...✅ 如[...set]
解构赋值✅ 如[a, b, ...rest] = iterable
Array.from()✅ 支持任意可迭代对象
yield*✅ 只能委托给可迭代对象
for await...of✅ 异步版本的基础

可以说,只要你用了这些现代语法,你就已经在享受迭代器协议带来的便利了。

架构意义:解耦“数据”与“访问方式”

传统遍历往往把逻辑和索引绑在一起,比如:

for (let i = 0; i < data.length; i++) { process(data[i]); }

一旦数据结构变了(比如换成树),整个循环就得重写。

而基于迭代器的方式则是:

for (const item of data) { process(item); }

无论data是数组、Set、自定义集合还是异步流,外部代码完全不用改。这就是关注点分离的力量。


最佳实践与性能建议

掌握了原理,最后分享一些来自实战的经验:

✅ 推荐做法

  • 为自定义集合类添加Symbol.iterator
    比如你写了个双向链表或二叉树,加上迭代器后用户可以直接for...of遍历节点。

  • 使用生成器简化复杂迭代逻辑
    特别适合递归结构(如树的遍历)、分页加载、事件流等场景。

  • 对无限序列做限制包装
    可以写个通用的take(iterable, n)工具函数:

```js
function* take(iterable, n) {
let count = 0;
for (const item of iterable) {
if (count++ >= n) return;
yield item;
}
}

// 使用
for (const x of take(fibonacci(), 5)) {
console.log(x);
}
```

❌ 避免踩的坑

  • 不要在next()中执行耗时同步操作
    否则会阻塞事件循环,影响用户体验。

  • 避免修改正在被迭代的数据结构
    比如一边遍历一边删除元素,可能导致状态错乱或跳过某些项。

  • 不要滥用无限生成器而不加控制
    即使是惰性求值,也要确保有明确的退出条件。


如果你现在回头去看文章开头提到的“巧克力盒子”比喻,是不是感觉豁然开朗?

迭代器协议就像那个“每次拿一颗”的规则,Symbol.iterator是盒子上的按钮,next()是你的手,value是拿到的巧克力,done是摸到底了没。

而 ES6 的伟大之处在于,它把这个简单的思想变成了语言级别的标准,让我们可以用统一的方式去面对千变万化的数据结构。

掌握它,不只是学会了一种语法,更是获得了一种思维方式 —— 如何将复杂问题拆解成“一步一步来”的流程。而这,正是构建健壮、可维护系统的关键所在。

如果你在项目中实现了有趣的可迭代类型,欢迎在评论区分享交流!

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

解锁音乐自由:ncmdump让网易云NCM格式转换变得如此简单

解锁音乐自由&#xff1a;ncmdump让网易云NCM格式转换变得如此简单 【免费下载链接】ncmdump 转换网易云音乐 ncm 到 mp3 / flac. Convert Netease Cloud Music ncm files to mp3/flac files. 项目地址: https://gitcode.com/gh_mirrors/nc/ncmdump 你是否曾经遇到过这样…

作者头像 李华
网站建设 2026/3/20 2:17:25

OllyDbg下载及安装:新手教程(零基础入门必看)

从零开始玩转逆向调试&#xff1a;手把手带你安全安装 OllyDbg 你是不是也曾在某篇技术文章里看到“用 OllyDbg 调试一下程序”这句话时&#xff0c;心里一紧—— 这玩意儿怎么装&#xff1f;哪里下&#xff1f;会不会中病毒&#xff1f; 别慌。今天我们就来彻底解决这个困…

作者头像 李华
网站建设 2026/3/10 17:05:53

UE4SS深度配置指南:从基础安装到高级功能定制

UE4SS深度配置指南&#xff1a;从基础安装到高级功能定制 【免费下载链接】RE-UE4SS Injectable LUA scripting system, SDK generator, live property editor and other dumping utilities for UE4/5 games 项目地址: https://gitcode.com/gh_mirrors/re/RE-UE4SS UE4S…

作者头像 李华
网站建设 2026/3/15 1:02:14

使用Mathtype编辑公式并通过CosyVoice3朗读讲解

使用 MathType 编辑公式并通过 CosyVoice3 实现智能语音讲解 在数字教育不断演进的今天&#xff0c;一个长期被忽视的问题逐渐浮出水面&#xff1a;如何让复杂的数学公式“被听见”&#xff1f; 对于视障学习者、远程学生&#xff0c;甚至是普通教师而言&#xff0c;仅仅看到“…

作者头像 李华
网站建设 2026/3/8 18:16:37

HS2游戏优化补丁完整安装指南:新手快速上手终极教程

HS2游戏优化补丁完整安装指南&#xff1a;新手快速上手终极教程 【免费下载链接】HS2-HF_Patch Automatically translate, uncensor and update HoneySelect2! 项目地址: https://gitcode.com/gh_mirrors/hs/HS2-HF_Patch HS2-HF_Patch是专为HoneySelect2游戏设计的强大…

作者头像 李华
网站建设 2026/3/10 17:12:16

MyBatisPlus枚举处理器优雅处理CosyVoice3状态字段

MyBatisPlus枚举处理器优雅处理CosyVoice3状态字段 在构建现代Java后端系统时&#xff0c;我们常常面临一个看似简单却极易引发问题的设计决策&#xff1a;如何表示和管理业务状态&#xff1f;尤其是在像CosyVoice3这样的AI语音合成平台中&#xff0c;任务从“待处理”到“生成…

作者头像 李华