踩坑实录:JS Map迭代器为什么“用完即焚”?兼谈技术知识沉淀

案发现场:第二次遍历怎么就"隐身"了?

前几天我在重构一段老代码时,遇到了一个让人极其挠头的 Bug。业务逻辑其实挺简单:用 Map 存了一批配置数据,我想先遍历一遍 keys() 做个前置校验,然后再遍历一遍执行具体的业务处理。

代码大概长这样:

javascript 复制代码
const myMap = new Map([
  ['a', 1],
  ['b', 2],
  ['c', 3]
]);

const keys = myMap.keys();

// 第一次遍历:做校验
for (const key of keys) {
  console.log('校验:', key);
}

// 第二次遍历:做业务
for (const key of keys) {
  console.log('处理:', key); // 这里居然没执行!
}

跑起来一看,控制台只打印了"校验",第二次循环直接静默了,仿佛 keys 里的数据凭空消失了。我当时第一反应是:难道我的 Map 被哪个异步操作给清空了?排查了半天,最后发现罪魁祸首竟然是 JavaScript 里的一个"隐形杀手"------迭代器对象。

根本原因:迭代器的"用完即焚"属性

很多人(包括曾经的我)会下意识地以为 Map.keys() 返回的是个数组,或者至少是个能反复读取的集合。毕竟平时用 Object.keys() 返回的就是个正儿八经的数组,想遍历几次就遍历几次。

Map 不一样。它的 keys()values()entries() 方法,返回的都是迭代器对象(Iterator)

在 JS 的底层设定里,迭代器是个"一次性消耗品"。它内部维护着一个指针,你每调用一次 next()(或者用 for...of 遍历),指针就往后挪一步。等遍历到尽头,返回 { done: true } 后,这个迭代器就彻底"耗尽"了。

你再去引用它?对不起,指针已经停在终点了,它再也吐不出任何新数据。这就是为什么第二次引用时,循环体根本不会执行。

划重点:Map 迭代器只能遍历一次,一旦耗尽,无法重置。

怎么破?两招搞定

既然摸清了它的脾气,对付起来就不难了。我总结了两个最常用的解法,大家可以根据实际场景按需取用。

1. 简单粗暴:转成数组

如果你的数据量不大,最直接、最省心的办法就是把它"拍扁"成数组。用扩展运算符 ... 或者 Array.from() 都可以。变成数组后,你想怎么遍历就怎么遍历。

javascript 复制代码
const myMap = new Map([['a', 1], ['b', 2]]);

// 用扩展运算符转成数组
const keysArray = [...myMap.keys()];

for (const key of keysArray) {
  console.log('校验:', key);
}

for (const key of keysArray) {
  console.log('处理:', key); // 完美执行
}

这种做法代码可读性最高,但代价是会在内存里额外创建一份数组副本。如果 Map 里有几十万条数据,这招可能会让你的内存飙升。

2. 按需生成:每次重新拿迭代器

如果数据量特别大,或者你对内存开销比较敏感,那就别缓存迭代器了。每次需要遍历的时候,重新调用一次 keys() 生成一个新的迭代器即可。

javascript 复制代码
const myMap = new Map([['a', 1], ['b', 2]]);

// 第一次遍历
for (const key of myMap.keys()) {
  console.log('校验:', key);
}

// 第二次遍历,重新调用 keys()
for (const key of myMap.keys()) {
  console.log('处理:', key);
}

这种做法没有额外的内存开销,但稍微牺牲了一点点代码的简洁度。不过权衡之下,在大数据量场景里,这绝对是更优雅的选择。

从踩坑到沉淀:技术笔记到底该写点啥?

解决完 Bug,故事其实还没完。

我见过不少开发者,踩完坑、改完代码,长舒一口气就切去打游戏了。但过几个月,换个项目,同样的坑他大概率还会再踩一次。这就是为什么我一直死磕技术写作与知识沉淀

写技术博客,从来不是为了凑字数拿全勤奖,而是给未来的自己(以及遇到同样问题的同行)留一份"避坑指南"。

像今天这个迭代器的坑,如果笔记里只干巴巴地写一句"Map 迭代器只能遍历一次",那太没营养了,过阵子你自己都看不懂。一份合格的知识沉淀,至少得包含这三个层次:

  • 还原案发现场:你是在什么业务场景下遇到的?贴出引发问题的代码。这能帮读者(包括未来的你)快速代入语境。
  • 深挖底层逻辑:为什么 JS 要这么设计?搞懂迭代器的指针机制,你以后遇到 Set、Generator 时就不会再犯同样的错。知其然,更得知其所以然。
  • 给出方案与权衡:转数组和重新生成各有什么优缺点?不要只给一个"标准答案",要提供决策依据,让读者知道在什么场景下该拔哪把刀。

技术写作本质上是对大脑碎片知识的一次"磁盘碎片整理"。当你试图把一个 Bug 的前因后果用大白话给别人讲清楚时,这个知识点才真正长在了你的脑子里。

所以,下次再遇到这种让你拍大腿的"小坑",别急着关掉编辑器。泡杯咖啡,把它写下来。毕竟,踩过的坑如果不变成文字,那就只是白踩了。

相关推荐
子兮曰12 小时前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰12 小时前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万12 小时前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝12 小时前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋13 小时前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁13 小时前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王952715 小时前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大15 小时前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师15 小时前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学16 小时前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端