在ES2022正式落地之前,JavaScript一直缺少真正意义上的私有属性。开发者要么靠命名约定,用下划线开头的属性名暗示“别碰我”,要么借助闭包、WeakMap等技巧模拟私有性,但这些都只是君子协定,外部代码依然可以随意读写。私有字段特性的出现改变了这一局面,通过井号(#)语法,语言层面第一次提供了无法从外部绕过的硬私有能力。本文将从基础用法、访问规则、与旧方案的对比以及常见误区几个角度,详细讲解这一特性。

私有字段的基本语法与访问规则
私有字段必须在使用前显式声明,这是它与传统属性最大的区别之一。你不能像给普通对象赋值那样,直接在构造函数里写一个带井号的属性,而必须在类体中先声明字段。声明的位置可以在constructor之前,也可以和其他类字段混排,但必须保证在使用前已经声明。这一点在写代码时容易被忽略,因为普通字段支持隐式创建,而私有字段不支持。
私有字段的命名以井号开头,例如#count。注意,井号是字段名的一部分,而不是修饰符。访问时必须带上井号,写作this.#count,如果在类外部访问,会直接抛出语法错误而不是返回undefined,这个设计非常关键。下面是一个完整示例:
class Counter {
// 声明私有字段
#count = 0;
#step;
constructor(step = 1) {
this.#step = step;
}
increment() {
this.#count += this.#step;
return this.#count;
}
// 提供只读访问器
get value() {
return this.#count;
}
}
const c = new Counter(2);
c.increment(); // 2
c.increment(); // 4
// c.#count 直接报 SyntaxError,外部根本访问不到还有一个细节值得注意:私有字段的检查必须基于this。如果你把一个私有字段相关的函数解构出来单独调用,由于函数内部的this不再是实例本身,访问私有字段会抛出TypeError。这和闭包方案的“天然绑定”不同,私有字段与实例绑定,而不是与函数绑定。
私有字段与私有方法、静态私有成员
除了实例字段,语言还支持私有方法和静态私有成员。私有方法的声明语法与私有字段类似,可以直接定义为普通方法、getter或者静态方法。私有方法可以访问同类中的私有字段,外部则完全不可见,适合用来封装内部计算逻辑,避免暴露不必要的接口。
class Order {
#items = [];
static #taxRate = 0.13;
add(item) {
this.#items.push(item);
}
// 私有方法:计算含税总价
#totalWithTax() {
const sum = this.#items.reduce((a, b) => a + b.price, 0);
return sum * (1 + Order.#taxRate);
}
checkout() {
return this.#totalWithTax().toFixed(2);
}
}
const o = new Order();
o.add({ price: 100 });
o.checkout(); // "113.00"静态私有字段#taxRate只能通过类本身访问,写作Order.#taxRate,不能通过实例访问。静态私有成员非常适合存放配置常量或缓存对象,既避免了全局污染,又不会被外部篡改。此外,私有字段支持in操作符检测,语法是#field in obj,可以在不触碰字段值的情况下判断某个对象是否拥有该私有字段,这在编写防御性代码时很有用。
需要提醒的是,私有字段不会出现在Object.keys、JSON.stringify的结果中,也无法被for...in遍历到。如果你依赖序列化来传输对象状态,私有字段会被静默丢弃,此时应该提供显式的toJSON或序列化方法。
与闭包、WeakMap方案的对比及避坑要点
在私有字段特性出现之前,主流模拟方案有两种。第一种是闭包:把变量藏在构造函数作用域里,通过方法访问。这种方式实现的私有性很彻底,但缺点是每个实例都要复制一份方法,无法享受原型共享带来的内存优势,子类继承也变得困难。第二种是WeakMap:用一个模块级的WeakMap以实例为键存储私有数据。这种方式保留了原型方法共享,内存占用更合理,WeakMap的弱引用特性也能避免内存泄漏,但代码繁琐,可读性差。
// WeakMap 模拟私有字段的旧写法
const secret = new WeakMap();
class User {
constructor(name, token) {
this.name = name;
secret.set(this, { token });
}
getToken() {
return secret.get(this).token;
}
}对比之下,私有字段语法兼具两者优点:方法仍然定义在原型上,私有数据与实例绑定,外部通过任何手段(包括Object.getOwnPropertySymbols、Proxy拦截)都无法窥探。Proxy无法代理对私有字段的访问,因为私有字段不走普通的属性查找路径,这是语言规范层面的保证。
最后是几个常见的坑。第一,私有字段不能通过点语法动态构造名称,你不能写obj["#count"]来访问,井号语法是编译期确定的。第二,同一个类中不能重复声明同名私有字段,子类和父类可以有同名私有字段,它们互不冲突,这一点其实是特性而非缺陷,方便子类重写内部实现而不影响父类逻辑。第三,访问不存在的私有字段会抛TypeError,所以对外暴露的公共方法内部要做好参数校验。综合来看,私有字段配合getter和Symbol,已经能覆盖绝大多数封装需求,建议在支持的环境(主流浏览器和Node 12以上)中优先使用原生语法,逐步淘汰下划线约定和WeakMap技巧,让封装意图在代码层面就清晰可辨。
JavaScript私有字段类封装#私有属性修改时间:2026-09-09 03:32:33