私有元素
私有元素是常规公共类元素的对应物,包括类字段、类方法等。私有元素通过使用哈希 #
前缀创建,不能在类外部合法引用。这些类元素的隐私封装由 JavaScript 本身强制执行。访问私有元素的唯一方法是通过点表示法,并且只能在定义私有元素的类中进行访问。
在此语法出现之前,私有元素并非语言原生。在原型继承中,其行为可以通过WeakMap
对象或闭包来模拟,但它们在人体工程学方面无法与 #
语法相提并论。
语法
class ClassWithPrivate {
#privateField;
#privateFieldWithInitializer = 42;
#privateMethod() {
// …
}
static #privateStaticField;
static #privateStaticFieldWithInitializer = 42;
static #privateStaticMethod() {
// …
}
}
还有一些额外的语法限制
- 在一个类中声明的所有私有标识符必须是唯一的。命名空间在静态元素和实例元素之间共享。唯一的例外是当两个声明定义一个 getter-setter 对时。
- 私有标识符不能是
#constructor
。
描述
大多数类元素都有它们的私有对应物
- 私有字段
- 私有方法
- 私有静态字段
- 私有静态方法
- 私有 getter
- 私有 setter
- 私有静态 getter
- 私有静态 setter
这些特性统称为私有元素。然而,JavaScript 中的构造函数不能是私有的。为了防止类在类外部被构造,你必须使用一个私有标志。
私有元素用# 名称(发音为“哈希名称”)声明,它们是带有 #
前缀的标识符。哈希前缀是属性名称的固有部分——你可以将其与旧的下划线前缀约定 _privateField
建立关系——但它不是普通的字符串属性,因此你不能使用方括号表示法动态访问它。
在类外部引用 #
名称是语法错误。引用未在类体中声明的私有元素,或尝试使用delete
删除已声明的元素也是语法错误。
class ClassWithPrivateField {
#privateField;
constructor() {
delete this.#privateField; // Syntax error
this.#undeclaredField = 42; // Syntax error
}
}
const instance = new ClassWithPrivateField();
instance.#privateField; // Syntax error
JavaScript 作为一种动态语言,能够执行这种编译时检查,因为它使用了特殊的哈希标识符语法,使其在语法层面与普通属性不同。
注意:在 Chrome 控制台中运行的代码可以访问类外部的私有元素。这是 DevTools 专属的 JavaScript 语法限制放宽。
如果你从不包含该元素的𝘦𝘮𝘰𝘫𝘪对象访问私有元素,则会抛出 TypeError
,而不是像普通属性那样返回 undefined
。
class C {
#x;
static getX(obj) {
return obj.#x;
}
}
console.log(C.getX(new C())); // undefined
console.log(C.getX({})); // TypeError: Cannot read private member #x from an object whose class did not declare it
此示例还说明你也可以在静态函数中访问私有元素,以及在类外部定义的实例上访问。
你可以使用 in
运算符检查外部定义的对象是否拥有私有元素。如果私有字段或方法存在,这将返回 true
,否则返回 false
。
class C {
#x;
constructor(x) {
this.#x = x;
}
static getX(obj) {
if (#x in obj) return obj.#x;
return "obj must be an instance of C";
}
}
console.log(C.getX(new C("foo"))); // "foo"
console.log(C.getX(new C(0.196))); // 0.196
console.log(C.getX(new C(new Date()))); // the current date and time
console.log(C.getX({})); // "obj must be an instance of C"
请注意私有名称始终预先声明且不可删除的推论:如果你发现一个对象拥有当前类的一个私有元素(无论是通过 try...catch
还是 in
检查),它就必须拥有所有其他私有元素。一个对象拥有一个类的私有元素通常意味着它是由该类构造的(尽管并非总是如此)。
私有元素不属于原型继承模型,因为它们只能在当前类的 body 中访问,并且不会被子类继承。不同类中同名的私有元素完全不同,并且彼此不兼容。将它们视为附加到每个实例的外部元数据,由类管理。因此,structuredClone()
不会克隆私有元素,并且Object.freeze()
和Object.seal()
对私有元素没有影响。
有关私有字段如何以及何时初始化的更多信息,请参阅公共类字段。
示例
私有字段
私有字段包括私有实例字段和私有静态字段。私有字段只能在类声明内部访问。
私有实例字段
与其公共对应物一样,私有实例字段
- 在基类中构造函数运行之前添加,或在子类中调用
super()
之后立即添加,并且 - 仅在类的实例上可用。
class ClassWithPrivateField {
#privateField;
constructor() {
this.#privateField = 42;
}
}
class Subclass extends ClassWithPrivateField {
#subPrivateField;
constructor() {
super();
this.#subPrivateField = 23;
}
}
new Subclass(); // In some dev tools, it shows Subclass {#privateField: 42, #subPrivateField: 23}
注意: ClassWithPrivateField
基类中的 #privateField
对 ClassWithPrivateField
来说是私有的,并且不能从派生类 Subclass
访问。
返回覆盖对象
类的构造函数可以返回一个不同的对象,该对象将用作派生类构造函数的新 this
。然后派生类可以在该返回对象上定义私有字段——这意味着可以将私有字段“标记”到不相关的对象上。
class Stamper extends class {
// A base class whose constructor returns the object it's given
constructor(obj) {
return obj;
}
} {
// This declaration will "stamp" the private field onto the object
// returned by the base class constructor
#stamp = 42;
static getStamp(obj) {
return obj.#stamp;
}
}
const obj = {};
new Stamper(obj);
// `Stamper` calls `Base`, which returns `obj`, so `obj` is
// now the `this` value. `Stamper` then defines `#stamp` on `obj`
console.log(obj); // In some dev tools, it shows {#stamp: 42}
console.log(Stamper.getStamp(obj)); // 42
console.log(obj instanceof Stamper); // false
// You cannot stamp private elements twice
new Stamper(obj); // Error: Initializing an object twice is an error with private fields
警告:这可能是一个非常令人困惑的操作。通常建议你避免从构造函数返回任何东西——尤其是与 this
无关的东西。
私有静态字段
与其公共对应物一样,私有静态字段
- 在类评估时添加到类构造函数中,并且
- 仅在类本身上可用。
class ClassWithPrivateStaticField {
static #privateStaticField = 42;
static publicStaticMethod() {
return ClassWithPrivateStaticField.#privateStaticField;
}
}
console.log(ClassWithPrivateStaticField.publicStaticMethod()); // 42
私有静态字段有一个限制:只有定义私有静态字段的类才能访问该字段。这在使用 this
时可能导致意外行为。在以下示例中,当我们尝试调用 Subclass.publicStaticMethod()
时,this
指向 Subclass
类(而不是 ClassWithPrivateStaticField
类),因此会导致 TypeError
。
class ClassWithPrivateStaticField {
static #privateStaticField = 42;
static publicStaticMethod() {
return this.#privateStaticField;
}
}
class Subclass extends ClassWithPrivateStaticField {}
Subclass.publicStaticMethod(); // TypeError: Cannot read private member #privateStaticField from an object whose class did not declare it
如果你用 super
调用方法,情况也是如此,因为super
方法不会以超类作为 this
调用。
class ClassWithPrivateStaticField {
static #privateStaticField = 42;
static publicStaticMethod() {
// When invoked through super, `this` still refers to Subclass
return this.#privateStaticField;
}
}
class Subclass extends ClassWithPrivateStaticField {
static callSuperMethod() {
return super.publicStaticMethod();
}
}
Subclass.callSuperMethod(); // TypeError: Cannot read private member #privateStaticField from an object whose class did not declare it
建议始终通过类名而不是 this
访问私有静态字段,这样继承就不会破坏该方法。
私有方法
私有方法包括私有实例方法和私有静态方法。私有方法只能在类声明内部访问。
私有实例方法
与其公共对应物不同,私有实例方法
- 在安装实例字段之前立即安装,并且
- 仅在类的实例上可用,而不在其
.prototype
属性上可用。
class ClassWithPrivateMethod {
#privateMethod() {
return 42;
}
publicMethod() {
return this.#privateMethod();
}
}
const instance = new ClassWithPrivateMethod();
console.log(instance.publicMethod()); // 42
私有实例方法可以是生成器、异步或异步生成器函数。私有 getter 和 setter 也可能,并且遵循与其公共getter 和setter 对应物相同的语法要求。
class ClassWithPrivateAccessor {
#message;
get #decoratedMessage() {
return `🎬${this.#message}🛑`;
}
set #decoratedMessage(msg) {
this.#message = msg;
}
constructor() {
this.#decoratedMessage = "hello world";
console.log(this.#decoratedMessage);
}
}
new ClassWithPrivateAccessor(); // 🎬hello world🛑
与公共方法不同,私有方法不能在其类的 .prototype
属性上访问。
class C {
#method() {}
static getMethod(x) {
return x.#method;
}
}
console.log(C.getMethod(new C())); // [Function: #method]
console.log(C.getMethod(C.prototype)); // TypeError: Receiver must be an instance of class C
私有静态方法
与其公共对应物一样,私有静态方法
- 在类评估时添加到类构造函数中,并且
- 仅在类本身上可用。
class ClassWithPrivateStaticMethod {
static #privateStaticMethod() {
return 42;
}
static publicStaticMethod() {
return ClassWithPrivateStaticMethod.#privateStaticMethod();
}
}
console.log(ClassWithPrivateStaticMethod.publicStaticMethod()); // 42
私有静态方法可以是生成器、异步和异步生成器函数。
先前为私有静态字段提到的相同限制对私有静态方法也适用,同样在使用 this
时可能导致意外行为。在以下示例中,当我们尝试调用 Subclass.publicStaticMethod()
时,this
指向 Subclass
类(而不是 ClassWithPrivateStaticMethod
类),因此会导致 TypeError
。
class ClassWithPrivateStaticMethod {
static #privateStaticMethod() {
return 42;
}
static publicStaticMethod() {
return this.#privateStaticMethod();
}
}
class Subclass extends ClassWithPrivateStaticMethod {}
console.log(Subclass.publicStaticMethod()); // TypeError: Cannot read private member #privateStaticMethod from an object whose class did not declare it
模拟私有构造函数
许多其他语言都包含将构造函数标记为私有的功能,这可以防止在类本身之外实例化该类——你只能使用创建实例的静态工厂方法,或者根本无法创建实例。JavaScript 没有原生方法可以做到这一点,但可以通过使用私有静态标志来实现。
class PrivateConstructor {
static #isInternalConstructing = false;
constructor() {
if (!PrivateConstructor.#isInternalConstructing) {
throw new TypeError("PrivateConstructor is not constructable");
}
PrivateConstructor.#isInternalConstructing = false;
// More initialization logic
}
static create() {
PrivateConstructor.#isInternalConstructing = true;
const instance = new PrivateConstructor();
return instance;
}
}
new PrivateConstructor(); // TypeError: PrivateConstructor is not constructable
PrivateConstructor.create(); // PrivateConstructor {}
规范
规范 |
---|
ECMAScript® 2026 语言规范 # prod-PrivateIdentifier |
ECMAScript® 2026 语言规范 # prod-00OK517S |
浏览器兼容性
javascript.classes.private_class_fields
加载中…
javascript.classes.private_class_fields_in
加载中…
javascript.classes.private_class_methods
加载中…
另见
- 使用类 指南
- 类
- 公共类字段
class
- TC39 class-fields 提案中的私有语法常见问题
- 所有 JS 类元素的语义 by Shu-yu Guo (2018)
- v8.dev 上的公共和私有类字段 (2018)