weakmap 是唯一能安全为不可扩展第三方对象(如 dom 节点、date、regexp)挂载私有状态的原生机制,键弱引用确保对象回收时状态自动清理,避免内存泄漏与兼容性问题。

WeakMap 是唯一能安全为第三方不可扩展对象(比如原生 DOM 节点、Date 实例、RegExp 对象等)挂载私有状态的原生机制。它不修改目标对象本身,不触发 Object.defineProperty 或 Proxy 的兼容性问题,也不污染原型链;更重要的是,状态生命周期与目标对象自动对齐——对象被回收,状态随之消失,无需手动清理。
为什么必须用 WeakMap,而不是其他方式
第三方对象通常不可扩展(Object.isExtensible(obj) === false),无法添加自有属性;强行使用 obj.__myState = {...} 会静默失败或抛错。普通对象字面量或 Map 作为缓存容器,则会强持有该对象,阻断垃圾回收——哪怕节点已从 DOM 移除、实例早已不用,只要还在 Map 里,整棵引用树就无法释放。
- ❌
cache.set(obj, state)(Map)→ 强引用,内存泄漏风险高 - ❌
obj.state = state(直接赋值)→ 多数原生对象禁止扩展,操作无效 - ❌
cache[obj] = state(普通对象)→ 键自动转字符串,所有同类对象共享同一 key - ✅
weakMap.set(obj, state)→ 键是弱引用,对象一不可达,条目自动失效
典型适用场景与操作要点
常见于封装第三方库行为、增强 DOM 元素能力、或桥接不同生命周期系统(如 Vue 组件卸载后仍需保留某段动画上下文)。
- 为
<canvas></canvas>缓存其getContext('2d')实例,仅在 canvas 存活时有效 - 给
new Date()实例标记“是否已被格式化过”,避免重复处理 - 在 MutationObserver 回调中为动态插入的
input元素绑定校验状态,移除即失效 - 为第三方 UI 组件实例(如某些未暴露 destroy 方法的库)附加清理钩子,确保 onUnmounted 时能执行对应逻辑
正确写法与关键检查项
WeakMap 不是“更弱的 Map”,它有一套必须遵守的契约:
- 键必须是对象:DOM 元素、Date、RegExp、自定义类实例都合法;字符串 ID、数字索引、
null或undefined会直接报TypeError - 务必先判空再 set:例如
const el = document.querySelector('.trigger'); if (el) weakMap.set(el, { active: true }); - 值可任意类型,但避免让值反向强引用键对象(如在值里存闭包并捕获
el),否则会间接延长键生命周期 - 不提供
size、keys()、entries()—— 它不是用来遍历的,而是“按需查、存在即有效”的元数据容器
一个轻量封装示例
你可以导出一个单例 WeakMap 并封装 get/set 辅助方法,提升可读性和健壮性:
(注意:不暴露原始 WeakMap,防止误操作)const elementState = (() => {
const wm = new WeakMap();
return {
get(el) {
if (!(el && typeof el === 'object')) return undefined;
return wm.get(el);
},
set(el, state) {
if (!(el && typeof el === 'object')) {
throw new TypeError('WeakMap key must be an object');
}
wm.set(el, state);
return this;
},
has(el) {
return !!el && typeof el === 'object' && wm.has(el);
}
};
})();
// 使用
elementState.set(canvasEl, { ctx: canvasEl.getContext('2d'), lastDraw: Date.now() });
const state = elementState.get(canvasEl); // 拿到即用










