闭包、柯里化、WeakMap:从前端到 V8 内存深渊的修炼


------小Dora 的 JavaScript 修炼日记 · Day 9

"闭包让变量活得更久,WeakMap 让你不会背上内存债。"

------一位调试 Chrome Heap Snapshot 的前端工程师


🗂 目录

  1. Map 与 WeakMap:表面区别 vs 内存差距
  2. 什么是弱引用?为什么 WeakMap 是弱引用?
  3. V8 内存结构:堆、栈、隐藏的引用网络
  4. Map 和 WeakMap 在 V8 的底层实现(ephemeron table 黑科技)
  5. 闭包 + Map = 内存泄漏?WeakMap 如何破局
  6. 柯里化与缓存实战:Map vs WeakMap 对比
  7. GC 工作原理:为什么 WeakMap 条目能"神秘消失"
  8. 实战:WeakMap 实现私有变量封装
  9. Day 9 自检 Checklist
  10. 彩蛋:如何用 Chrome DevTools 看 WeakMap 条目回收

1. Map 与 WeakMap:表面区别 vs 内存差距

先看表格(有内涵的表格):

特性 Map WeakMap
键类型 任意类型(对象、字符串、Symbol) 只能是对象
引用类型 强引用(阻止键对象被 GC) 弱引用(不阻止键对象被 GC)
可遍历性 ✔ 支持 forEachkeys() ❌ 不可遍历(因为 GC 随时可能清理条目)
GC 行为 只要 Map 活着,键对象就活着 如果键对象无其他引用,GC 会清理 WeakMap 条目

🤔 那问题来了

为什么 WeakMap 不能遍历?因为 V8 不保证弱引用键一定存在,遍历会破坏 GC 设计。


2. 什么是弱引用?为什么 WeakMap 是弱引用?

强引用:

"你在对象身上挂了个标签:你不能死,你死了我也完蛋。"

普通 Map 的键就是强引用:

javascript 复制代码
Global → Map → KeyObject → (堆)

GC 扫描时,发现 KeyObject 可达,就不会回收。


弱引用:

"你在对象身上写了个字条:你死了我不管,保重。"

WeakMap 对键是弱引用,GC 会忽略 WeakMap → 键的指针,如果对象没有其他强引用,直接回收。


⚠️ 关键区别:

  • Map: 只要 Map 活着,键对象活到天荒地老。
  • WeakMap: 如果外部没人引用键对象,GC 直接干掉它,WeakMap 条目同步消失。

3. V8 内存结构:堆、栈、隐藏的引用网络

栈(Stack)

  • 存放执行上下文、基本类型、指针。
  • 生命周期短,自动释放。

堆(Heap)

  • 存放对象、闭包、函数、Map、WeakMap。
  • V8 垃圾回收器(GC)负责回收不可达对象。

GC 的核心算法:可达性分析

  • 根对象(全局对象、当前栈变量)出发,沿引用链标记所有能到达的对象。
  • 没有被标记的,就是"垃圾"。

4. Map 和 WeakMap 在 V8 的底层实现

Map 用的就是普通哈希表,存储的是:

yaml 复制代码
struct MapEntry {
  Key: HeapObject* (强引用)
  Value: HeapObject* (强引用)
}

WeakMap 则用了 Ephemeron Table(短命关联表)

  • 键:弱引用(GC 标记阶段会忽略它)。
  • 值:强引用,但依附于键的生死。

V8 GC 扫描时:

  • 如果键不可达 → 清理键和值。
  • 如果键可达 → 值继续保留。

为什么 WeakMap 不可遍历?

因为 V8 不维护完整的引用图,条目随 GC 消失,遍历结果无法确定。


5. 闭包 + Map = 内存泄漏?WeakMap 如何破局

来看个典型问题:

ini 复制代码
function createCache() {
  const cache = new Map();
  return function (obj, val) {
    if (cache.has(obj)) return cache.get(obj);
    cache.set(obj, val);
  };
}

const save = createCache();
let user = { name: 'Dora' };

save(user, 'VIP');
// user = null; ❌ 即使置空,Map 仍引用 user

Map 强引用 user → GC 不能清理 → 内存泄漏

改成 WeakMap:

ini 复制代码
function createCache() {
  const cache = new WeakMap();
  return function (obj, val) {
    if (cache.has(obj)) return cache.get(obj);
    cache.set(obj, val);
  };
}

let user = { name: 'Dora' };
const save = createCache();

save(user, 'VIP');
user = null; // ✅ GC 回收 user 和 WeakMap 条目

6. 柯里化与缓存实战

普通缓存(Map 版)

vbnet 复制代码
function curry(fn) {
  const cache = new Map();
  return function (...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = fn(...args);
    cache.set(key, result);
    return result;
  };
}

问题:cache 持久化 → 永不释放。


WeakMap 缓存

php 复制代码
function curryWithWeakMap(fn) {
  const cache = new WeakMap();
  return function (obj) {
    if (cache.has(obj)) return cache.get(obj);
    const result = fn(obj);
    cache.set(obj, result);
    return result;
  };
}

✅ 对象没引用了,条目也没了,闭包安全 + 内存安全


7. GC 工作原理:为什么 WeakMap 条目能"神秘消失"

V8 GC 在标记阶段:

  • 遍历根 → 标记强引用对象。

  • 遍历弱引用集合(ephemeron tables):

    • 如果键未被标记 → 清理键和值。

因此,WeakMap 条目的生命周期完全取决于键对象的可达性

简化伪代码:

scss 复制代码
if (!isMarked(key)) {
  clearEntry(key, value);
}

8. WeakMap 实现私有变量封装

javascript 复制代码
const _private = new WeakMap();

class Person {
  constructor(name) {
    _private.set(this, { name });
  }
  getName() {
    return _private.get(this).name;
  }
}

let p = new Person('Dora');
console.log(p.getName()); // Dora
p = null; // ✅ 对象销毁,WeakMap 自动清理条目

9. Day 9 自检 Checklist

✔ 我能解释 Map 和 WeakMap 的区别,尤其是 GC 行为吗?

✔ 我知道什么是弱引用,为什么 GC 可以忽略它吗?

✔ 我理解 V8 的 ephemeron table 是如何实现 WeakMap 吗?

✔ 我能用 WeakMap 优化闭包缓存,防止内存泄漏吗?

✔ 我能在 Chrome DevTools 验证 WeakMap 条目回收吗?


🎁 彩蛋:用 Chrome 验证 WeakMap 条目回收

  1. 打开 Memory → Heap Snapshot。
  2. 创建 WeakMap 并插入对象。
  3. 清空对象引用 → 强制 GC → 查看条目是否消失。

🔥 一句话总结

Map 和 WeakMap 的区别,不只是"能不能 GC",而是 V8 背后的引用策略 + ephemeron table 机制 。理解它,才能写出性能安全的闭包缓存


相关推荐
正义的大古15 小时前
OpenLayers地图交互 -- 章节十一:拖拽文件交互详解
javascript·vue.js·microsoft·openlayers
清木Moyu15 小时前
layui tree组件回显bug问题,父级元素选中导致子集全部选中
前端·bug·layui
奶糖 肥晨15 小时前
前端Bug实录:为什么表格筛选条件在刷新时神秘消失?
前端·bug
樱花落海洋11115 小时前
layui 表格行级 upload 上传操作
前端·javascript·layui
艾小码15 小时前
告别复制粘贴!掌握这7个原则,让你的Vue组件复用性翻倍
前端·javascript·vue.js
白露与泡影17 小时前
2025年高质量Java面试真题汇总
java·python·面试
꒰ঌ 安卓开发໒꒱17 小时前
Java 面试 -Java基础
java·开发语言·面试
我是ed18 小时前
# vite + vue3 实现打包后 dist 文件夹可以直接打开 html 文件预览
前端
小白640219 小时前
前端梳理体系从常问问题去完善-工程篇(webpack,vite)
前端·webpack·node.js
不老刘19 小时前
从构建工具到状态管理:React项目全栈技术选型指南
前端·react.js·前端框架