WeakMap 比闭包更适配多实例场景,因其以实例为 key 实现天然多实例支持且能随实例自动 GC;而闭包在多实例下易致内存滞留。

直接用 Map 模拟私有属性,核心是把实例作为 key、私有数据作为 value 存进一个模块作用域内的 Map 中。这样外部既无法访问该 Map,也无法通过常规手段枚举或修改绑定的私有状态。
为什么 Map(尤其是 WeakMap)比闭包更适配多实例场景
闭包适合单例工具(如 storageUtil),但遇到多个类实例时,每个实例都靠独立闭包捕获变量,容易造成内存滞留;而 WeakMap 以实例为 key,天然支持多实例,且能随实例被垃圾回收自动清理关联数据,避免内存泄漏。
- WeakMap 的 key 必须是对象,且是弱引用 —— 实例销毁后,对应 entry 不会阻止 GC
- 普通 Map 用对象作 key 是强引用,若忘记手动 delete,会导致内存泄漏
- 所有私有操作必须通过统一 WeakMap 实例进行,不能在每个构造函数里 new 一个
标准写法:WeakMap + 构造函数绑定
在模块顶层声明一次 WeakMap,每次 new 实例时调用 set 绑定私有数据对象;所有方法内部都通过 get(this) 获取专属状态。
- 初始化 WeakMap:
const privateData = new WeakMap(); - 构造函数中绑定:
privateData.set(this, { count: 0, config: {} }); - 实例方法中读写:
const data = privateData.get(this); data.count++; - 不返回私有数据的引用,防止外部意外修改内部结构
与其它“伪私有”方式的关键区别
Symbol 和 # 字段看似简洁,但各有硬伤:Symbol 可被 Object.getOwnPropertySymbols() 拿到,不是真正隔离;# 字段虽语法级私有,但仅限类内使用,无法在工厂函数、Mixin 或旧环境(如 IE、部分 Node.js 版本)中运行。
- WeakMap 方案兼容所有 ES6+ 环境,Node.js 6.0+、Chrome 49+、Firefox 42+ 均支持
- 它不依赖语法糖,逻辑清晰,调试时可通过 console.log(privateData) 查看当前绑定关系(仅限开发)
- 适合封装工具类、事件管理器、状态缓存器等需生命周期对齐的模块
一个可复用的私有封装模板
以下是一个带私有计数器和配置的类封装示例,可直接用于生产环境:
- 模块顶层:
const _privates = new WeakMap(); - 类定义:
class Counter { constructor(cfg = {}) { _privates.set(this, { count: 0, cfg }); } increment() { const p = _privates.get(this); p.count++; return p.count; } getConfig() { return Object.assign({}, _privates.get(this).cfg); } } - 关键点:getConfig 返回副本,避免外部篡改原始配置










