私有字段
基于现代 JavaScript(ES2025 · 核于 2026-06)
速查
#field(ES2022):硬私有实例字段,必须先在类体声明,类外访问obj.#x是语法错误- 硬私有:JavaScript 不提供任何「绕过」机制(不同于
_name这种纯约定) - 同类实例互通:方法里可读写另一个同类实例的私有字段
other.#x - 不可动态创建、不可删除、不可重名:均为早期语法错误;不能
delete this.#x #method():私有方法,类外不可调用;同理有私有get #x()/set #x()访问器static #x/static #m():私有静态成员(见 静态成员)- 品牌检查:
#x in obj(ES2022)安全判断「obj是不是本类实例」,不抛错、返回布尔 - 不继承:子类访问不到父类的
#field - 误区:
#不是「下划线约定」的新写法——_x仍是公开属性,只有#x才真正私有
#field:真正的私有
在 ES2022 之前,JavaScript 没有语言级私有,社区只能靠 _name 这种命名约定「假装私有」——但谁都能 obj._name 直接读写。# 前缀的私有字段补上了这块:
js
class BankAccount {
#balance = 0; // 私有字段,必须先声明
deposit(amount) {
this.#balance += amount;
return this.#balance;
}
get balance() {
return this.#balance; // 只读对外暴露
}
}
const acc = new BankAccount();
acc.deposit(100);
console.log(acc.balance); // 100
// console.log(acc.#balance); // SyntaxError:Private field '#balance' must be declared in an enclosing class它是硬私有:和某些语言「私有只是约定、反射仍能访问」不同,JavaScript 不提供任何从类外读取 # 字段的途径。
#x 必须先声明,且不能用下划线代替
两个常被混淆的点:
- 必须声明:用到的每个
#x都要在类体里出现声明,不能像普通属性那样在构造函数里凭空this.#x = 1创建一个未声明的私有字段。 _x≠ 私有:this._balance只是个普通公开属性,命名上提示「别碰」,但语言层面毫无保护。真要私有,必须用#。
同类实例之间可以互访
私有性是「按类」而非「按实例」隔离的——在类的方法内部,可以访问另一个同类实例的私有字段。这让「比较两个对象」这类操作能拿到对方的内部状态:
js
class Money {
#cents;
constructor(cents) {
this.#cents = cents;
}
// 访问参数对象的私有字段(同类)
add(other) {
return new Money(this.#cents + other.#cents);
}
}
const a = new Money(150);
const b = new Money(250);
console.log(a.add(b)); // Money { #cents: 400 }私有方法与私有访问器
不只是字段,方法和访问器也能私有——# 前缀的方法只能在类体内调用,适合「不想暴露给外部的内部步骤」:
js
class Counter {
#count = 0;
// 私有访问器
get #value() {
return this.#count;
}
set #value(v) {
this.#count = v;
}
// 私有方法
#render() {
return `当前:${this.#value}`;
}
// 公有方法编排私有逻辑
tick() {
this.#value++;
return this.#render();
}
}
const c = new Counter();
console.log(c.tick()); // "当前:1"
// c.#render(); // SyntaxError:类外不可调用私有方法#x in obj:安全的品牌检查
直接对「不确定是不是本类实例」的对象访问 obj.#x 会抛错(而非返回 undefined)。ES2022 引入了 #x in obj 这个**品牌检查(brand check)**语法,用来安全判断某对象是否「带有本类的私有字段」,返回布尔、不抛错:
js
class Color {
#values;
constructor(r, g, b) {
this.#values = [r, g, b];
}
redDifference(other) {
// 先确认 other 确实是 Color 实例,再访问其私有字段
if (!(#values in other)) {
throw new TypeError("需要一个 Color 实例");
}
return this.#values[0] - other.#values[0];
}
}
const a = new Color(255, 0, 0);
const b = new Color(100, 0, 0);
console.log(a.redDifference(b)); // 155
console.log(#values in a); // true(只能写在类体内)
console.log(#values in {}); // false —— 普通对象没有这个品牌品牌检查的价值:它比 instanceof 更可靠——instanceof 可被 Symbol.hasInstance 或原型篡改欺骗(见 访问器与 instanceof),而「是否拥有某私有字段」无法伪造,是判断「真的由本类构造出来」的硬证据。
几条硬性限制
- 不可重名:同一个类里
#x只能声明一次,重复声明是语法错误。 - 不可删除:
delete this.#x是语法错误(普通属性可delete,私有字段不行)。 - 不可动态访问:没有
obj[#x]这种动态形式,私有名不是字符串属性键。 - 不继承:子类拿不到父类的
#field——私有性不沿继承链开放(见 继承与 super)。
接下来
封装的另一面是「受控地暴露」——用访问器把内部状态包装成像属性一样读写的接口,并搞清类型判定。下一页:访问器与 instanceof,讲 get/set、instanceof、Symbol.hasInstance 定制与 new.target。