javascript 中的 # 前缀用于声明私有字段和方法,是 es2022 正式引入的运行时强制封装机制,外部无法访问、探测或绕过,且不参与枚举、反射或序列化。

JavaScript 中的 # 前缀用于声明私有字段(和私有方法),这是 ECMAScript 2022(ES13)正式引入的语法,能实现运行时强制的真正封装——外部无法访问、无法探测、无法绕过,连 Reflect.ownKeys() 和 for...in 都看不到它。
私有字段的声明与访问规则
以 # 开头的标识符只能在类定义内部使用,且必须在类体中显式声明(不能动态添加):
- 声明必须写在类体顶层,如
#count = 0;或#name;(可选初始化) - 只能在类的实例方法、getter/setter、构造函数中通过
this.#xxx访问 - 子类无法继承或访问父类的 # 字段——它们完全不可见,不是“受保护”,而是“不存在于子类作用域”
- 尝试在类外读写(包括通过
obj.#xxx)、或在其他类/函数中访问,会直接抛出SyntaxError或TypeError
为什么比 Symbol 或闭包更“真正”?
对比传统模拟方式:
-
Symbol 模拟:属性仍存在于对象上,可用
Object.getOwnPropertySymbols()枚举,也能被反射 API 操作 - 闭包模拟:字段不挂载在实例上,但每次创建实例都生成新函数,内存开销大;且无法在原型方法中访问(因为闭包绑定的是构造时的作用域)
-
# 字段:字段物理存在于实例内部,但 JS 引擎将其隔离在私有名称空间;不参与任何公开的属性枚举、遍历或反射,
JSON.stringify()自动忽略,Object.assign()不复制
实用限制与注意事项
# 字段能力强大,但也有明确边界:
- 不能用作对象键(
{ [#key]: 'value' }❌),也不能解构(const { #x } = obj❌) - 私有方法同样用 # 声明,可被同个类的其他方法调用,但不可被子类重写或访问
- 目前不支持私有静态字段(
static #x)——该语法已在 ES2022 提案中,但主流引擎(Chrome 120+、Node.js 20.6+)已支持 - 调试时 DevTools 可能显示 # 字段(取决于浏览器版本),但脚本中仍无法访问
一个典型应用示例
封装一个带校验的计数器,防止外部篡改内部状态:
class Counter {
#value = 0;
#step = 1;
<p>constructor(step = 1) {
if (step </p><p>increment() {
this.#value += this.#step;
}</p><p>get value() {
return this.#value; // 只读暴露
}</p><p>reset() {
this.#value = 0;
}
}</p><p>const c = new Counter(2);
c.increment(); // #value → 2
console.log(c.value); // 2
console.log(c.#value); // ❌ SyntaxError: Private field '#value' must be declared in an enclosing class
</p>
这种封装不依赖约定、不依赖文档警示,是语言级保障。只要用 #,就真的安全。











