闭包、柯里化、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 机制 。理解它,才能写出性能安全的闭包缓存


相关推荐
2301_7816686114 分钟前
前端基础 JS Vue3 Ajax
前端
上单带刀不带妹36 分钟前
前端安全问题怎么解决
前端·安全
Fly-ping39 分钟前
【前端】JavaScript 的事件循环 (Event Loop)
开发语言·前端·javascript
SunTecTec1 小时前
IDEA 类上方注释 签名
服务器·前端·intellij-idea
大佐不会说日语~1 小时前
Redis高可用架构演进面试笔记
redis·面试·架构
在逃的吗喽2 小时前
黑马头条项目详解
前端·javascript·ajax
袁煦丞2 小时前
有Nextcloud家庭共享不求人:cpolar内网穿透实验室第471个成功挑战
前端·程序员·远程工作
小磊哥er2 小时前
【前端工程化】前端项目开发过程中如何做好通知管理?
前端
拾光拾趣录2 小时前
一次“秒开”变成“转菊花”的线上事故
前端
你我约定有三2 小时前
前端笔记:同源策略、跨域问题
前端·笔记