JavaScript模拟私有状态有三种主流方式:闭包封装(构造函数内声明变量并暴露方法)、工厂函数(返回对象,语义清晰)、WeakMap(键为实例,支持原型复用且垃圾回收友好),但均为约定式保护,新项目应优先使用ES2022私有类字段#field。

在私有类字段(#field)提案被广泛支持之前,JavaScript 开发者常用闭包来模拟类的私有状态,避免外部直接访问或修改内部数据。
利用构造函数 + 闭包封装私有变量
通过在构造函数作用域内声明变量,并只暴露有限的公有方法(这些方法能访问闭包中的变量),实现“隐藏”数据的效果。
- 私有变量不会挂载到
this上,无法被实例外部读取或篡改 - 所有实例方法都共享同一份闭包环境,可安全操作私有数据
- 注意:每个实例都会创建独立的闭包,内存开销略高于原型方法
示例:
function Counter() {
let count = 0; // 私有变量,外部不可访问
this.increment = () => count++;
this.getValue = () => count;
this.reset = () => { count = 0; };
}
const c1 = new Counter();
console.log(c1.getValue()); // 0
c1.increment();
console.log(c1.getValue()); // 1
console.log(c1.count); // undefined —— 无法直接访问
工厂函数模式:更清晰的私有/公有分离
不依赖 new 和 this,用纯函数返回对象,语义更明确,也更容易控制哪些是私有、哪些是公有。
立即学习“Java免费学习笔记(深入)”;
- 适合轻量级、无继承需求的对象创建
- 私有逻辑完全隔离,连
this绑定问题都不存在 - 可配合模块模式,在模块顶层闭包中定义跨实例共享的私有工具函数
示例:
function createCounter(initial = 0) {
let count = initial;
return {
increment() { count++; },
decrement() { count--; },
get() { return count; }
};
}
const counter = createCounter(5);
counter.increment();
console.log(counter.get()); // 6
console.log(counter.count); // undefined
WeakMap 实现真正的“实例私有”存储
利用 WeakMap 将私有数据与实例对象关联,既避免污染实例属性,又支持原型方法复用(比闭包更省内存)。
- 私有数据存在
WeakMap中,键是实例对象,值是私有数据对象 - 方法可定义在原型上,多个实例共享方法,但各自拥有独立私有状态
- 垃圾回收友好:实例被销毁后,
WeakMap中对应条目自动释放
示例:
const privateData = new WeakMap();
class Counter {
constructor(initial = 0) {
privateData.set(this, { count: initial });
}
increment() {
const data = privateData.get(this);
data.count++;
}
getValue() {
return privateData.get(this).count;
}
}
注意事项与局限性
这些方案虽能模拟私有性,但本质仍是“约定式保护”,无法阻止反射或调试时的访问。
-
console.dir(instance)可能暴露闭包变量(尤其在 DevTools 中) -
WeakMap的私有数据无法被JSON.stringify序列化 - 类型系统(如 TypeScript)对闭包私有变量的支持有限,需额外声明或使用
declare - 私有类字段(
#field)如今已是 ES2022 正式特性,新项目优先使用它










