Map 和 Set 本身不导致内存泄漏,但误用会引发问题:用对象/DOM 元素作 key、未清理事件或定时器引用、忽略生命周期清理等。应优先使用 WeakMap/WeakSet,监控 Retained Size,检查 Retainers 链定位根源。

Map 和 Set 本身不是内存泄漏的根源,但用错场景或持有不当引用,会成为“内存黑洞”的帮凶。排查关键不在结构本身,而在它持有了什么、被谁持有、是否该活这么久。
盯住 Retained Size 异常高的 Map/Set 实例
在 Chrome DevTools 的 Memory 面板中拍堆快照(Heap Snapshot),筛选构造函数为 Map 或 Set 的对象,重点关注:
- Retained Size 超过几 MB(尤其 >5MB)的实例
- 同一构造函数下数量持续增长(比如每次操作新增一个 Map)
- Size 属性很大但实际业务逻辑并不需要存这么多数据
点击可疑对象 → 右侧 Retainers 面板,顺着引用链往上找“不该活这么久”的宿主——常见源头是模块顶层变量、未清理的闭包、全局事件监听器。
检查 Map 是否误作只读配置缓存
Map 设计用于动态增删查,不是静态配置容器。若写成:
const configMap = new Map([ ['theme', 'dark'], ['lang', 'zh-CN'] ]); // 模块级常量,但 key 是对象或函数?
问题在于:如果后续不小心把 DOM 元素、未销毁的组件实例、带大 payload 的回调函数作为 key 存入,整个 Map 就成了强引用锚点,阻止 GC 回收。
- ✅ 正确做法:只存轻量、无副作用的 key(如 string、number、Symbol)
- ❌ 错误信号:key 是
{ id: 1 }、document.querySelector('...')、() => {...}等不可控生命周期的对象 - ? 替代方案:若需以 DOM 或对象为键做映射,改用 WeakMap(仅支持对象键,且不阻止 GC)
排查 Set 在事件/状态管理中隐式累积
Set 常被用来记录“已点击”“已加载”“已处理”等状态,但容易忽略清理时机:
- 给列表项绑定 click 时,用
set.add(e.target)记录点击过的按钮,但列表重新渲染后旧 DOM 节点仍被 Set 持有 - 定时任务中不断
set.add(new Date())或set.add(largeObj),却从不clear()或按条件delete() - 组件内创建 Set 存储临时数据,但组件卸载后未清空或销毁引用
这类用法会让 Set 成为“只进不出”的数据池,Retained Size 随时间线性上涨。建议:
- 明确 Set 的生命周期,配合 dispose / cleanup 阶段调用
set.clear() - 避免用 DOM 节点、React/Vue 组件实例等易失效对象作为 Set 元素
- 真要关联 DOM,优先考虑 WeakSet(只接受对象,且不阻止回收)
验证是否被闭包或定时器意外延长寿命
一个看似干净的 Map/Set,可能因外部闭包或未清除的定时器被长期持有:
- 在
setInterval回调里访问模块级cacheMap,同时回调又闭包了大数组或响应体 blob - 事件监听器中调用
cacheMap.set(key, bigData),但忘记在removeEventListener前清空对应条目 - 异步请求成功回调里往全局 Set 添加结果,但请求失败或页面跳转后无人清理
这类问题往往不会报错,但内存占用缓慢爬升。排查时重点看:
- Retainers 链中是否出现
setTimeout、setInterval、EventListener、Closure - 这些引用是否属于已退出的上下文(如已卸载的 React 组件、已关闭的 Tab)











