WeakMap 是 ES2022 前实现“真”私有属性最可靠的方式,因其键弱引用确保自动垃圾回收,且需配合闭包封装于模块顶层作用域,避免误用导致失效。

WeakMap 是 ES2022 之前实现“真”私有属性最可靠的方式——它让私有数据既无法被外部访问,又不会拖慢垃圾回收。
为什么 WeakMap 能做到“真”私有
普通对象或 Map 存私有数据时,实例会被强引用住,哪怕对象早已不用,内存也释放不掉。WeakMap 的键是弱引用:只要实例没被其他地方持有,它连带在 WeakMap 中的条目就会自动消失。这不是靠约定、不是靠下划线命名,而是由 JS 引擎保障的隔离与清理。
但 WeakMap 本身不提供封装——它得和闭包配合:WeakMap 实例必须藏在模块级(或函数级)作用域里,不让外部拿到;每个类独占一个 WeakMap,确保不同类的数据互不干扰。
正确组织 WeakMap 的位置和结构
WeakMap 必须声明在类定义之外、模块顶层(或立即执行函数内),绝不能写在 class 内部或构造函数里:
- ❌ 写在 constructor 里 → 每次 new 都新建 WeakMap,完全失效
- ❌ 写在 class {} 里 → 语法错误,ES 不允许
- ✅ 声明在模块顶层 → 单例复用,所有实例共享同一张表
- ✅ 用立即执行函数包裹类 → 更严格地封住 WeakMap,适合多类共存场景
示例:
const privateStore = new WeakMap();class User {
constructor(name) {
privateStore.set(this, { _name: name, _id: Math.random() });
}
getName() {
const data = privateStore.get(this);
return data ? data._name : null;
}
}
避免 get 返回 undefined 的实用技巧
WeakMap.get(this) 返回 undefined,通常不是引擎问题,而是使用疏漏:
- 构造函数中漏掉 set —— 必须在 this 初始化后立刻调用 privateStore.set(this, {...})
- this 指向丢失 —— 方法解构(如 const fn = user.getName)或传给 setTimeout 会导致 this 变成 undefined,WeakMap.get(undefined) 必然失败
- 读取前加防护:if (!privateStore.has(this)) throw new Error('Invalid instance')
- 绑定 this 推荐用 .bind(user) 或箭头函数包装,避免隐式调用失联
和 # 私有字段的本质区别
ES2022 的 # 语法是语言级私有,编译期检查、无需手动管理存储结构;而 WeakMap 方案是运行时机制,依赖开发者组织好作用域和生命周期。它不提供语法报错(比如 user.#name 会直接 SyntaxError,而 user._name 只是拿不到值),但它兼容所有 ES6+ 环境,且天然支持私有方法、私有状态标记、缓存等复杂场景——只要把函数也塞进 WeakMap 存储的对象里即可。

















