私有元素

私有元素是常规公共类元素的对应物,包括类字段、类方法等。私有元素通过使用哈希 # 前缀创建,不能在类外部合法引用。这些类元素的隐私封装由 JavaScript 本身强制执行。访问私有元素的唯一方法是通过点表示法,并且只能在定义私有元素的类中进行访问。

在此语法出现之前,私有元素并非语言原生。在原型继承中,其行为可以通过WeakMap 对象或闭包来模拟,但它们在人体工程学方面无法与 # 语法相提并论。

注意:在 MDN 上,我们避免使用“私有属性”一词。JavaScript 中的属性具有字符串或符号键,并具有 writableenumerableconfigurable 等特性,但私有元素没有这些特性。虽然私有元素通过熟悉的点表示法访问,但它们不能被代理、枚举、删除,也不能使用任何Object方法进行交互。

语法

js
class ClassWithPrivate {
  #privateField;
  #privateFieldWithInitializer = 42;

  #privateMethod() {
    // …
  }

  static #privateStaticField;
  static #privateStaticFieldWithInitializer = 42;

  static #privateStaticMethod() {
    // …
  }
}

还有一些额外的语法限制

  • 在一个类中声明的所有私有标识符必须是唯一的。命名空间在静态元素和实例元素之间共享。唯一的例外是当两个声明定义一个 getter-setter 对时。
  • 私有标识符不能是 #constructor

描述

大多数类元素都有它们的私有对应物

  • 私有字段
  • 私有方法
  • 私有静态字段
  • 私有静态方法
  • 私有 getter
  • 私有 setter
  • 私有静态 getter
  • 私有静态 setter

这些特性统称为私有元素。然而,JavaScript 中的构造函数不能是私有的。为了防止类在类外部被构造,你必须使用一个私有标志

私有元素用# 名称(发音为“哈希名称”)声明,它们是带有 # 前缀的标识符。哈希前缀是属性名称的固有部分——你可以将其与旧的下划线前缀约定 _privateField 建立关系——但它不是普通的字符串属性,因此你不能使用方括号表示法动态访问它。

在类外部引用 # 名称是语法错误。引用未在类体中声明的私有元素,或尝试使用delete删除已声明的元素也是语法错误。

js
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

js
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

js
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() 之后立即添加,并且
  • 仅在类的实例上可用。
js
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 基类中的 #privateFieldClassWithPrivateField 来说是私有的,并且不能从派生类 Subclass 访问。

返回覆盖对象

类的构造函数可以返回一个不同的对象,该对象将用作派生类构造函数的新 this。然后派生类可以在该返回对象上定义私有字段——这意味着可以将私有字段“标记”到不相关的对象上。

js
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 无关的东西。

私有静态字段

与其公共对应物一样,私有静态字段

  • 在类评估时添加到类构造函数中,并且
  • 仅在类本身上可用。
js
class ClassWithPrivateStaticField {
  static #privateStaticField = 42;

  static publicStaticMethod() {
    return ClassWithPrivateStaticField.#privateStaticField;
  }
}

console.log(ClassWithPrivateStaticField.publicStaticMethod()); // 42

私有静态字段有一个限制:只有定义私有静态字段的类才能访问该字段。这在使用 this 时可能导致意外行为。在以下示例中,当我们尝试调用 Subclass.publicStaticMethod() 时,this 指向 Subclass 类(而不是 ClassWithPrivateStaticField 类),因此会导致 TypeError

js
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 调用

js
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 属性上可用。
js
class ClassWithPrivateMethod {
  #privateMethod() {
    return 42;
  }

  publicMethod() {
    return this.#privateMethod();
  }
}

const instance = new ClassWithPrivateMethod();
console.log(instance.publicMethod()); // 42

私有实例方法可以是生成器、异步或异步生成器函数。私有 getter 和 setter 也可能,并且遵循与其公共gettersetter 对应物相同的语法要求。

js
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 属性上访问。

js
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

私有静态方法

与其公共对应物一样,私有静态方法

  • 在类评估时添加到类构造函数中,并且
  • 仅在类本身上可用。
js
class ClassWithPrivateStaticMethod {
  static #privateStaticMethod() {
    return 42;
  }

  static publicStaticMethod() {
    return ClassWithPrivateStaticMethod.#privateStaticMethod();
  }
}

console.log(ClassWithPrivateStaticMethod.publicStaticMethod()); // 42

私有静态方法可以是生成器、异步和异步生成器函数。

先前为私有静态字段提到的相同限制对私有静态方法也适用,同样在使用 this 时可能导致意外行为。在以下示例中,当我们尝试调用 Subclass.publicStaticMethod() 时,this 指向 Subclass 类(而不是 ClassWithPrivateStaticMethod 类),因此会导致 TypeError

js
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 没有原生方法可以做到这一点,但可以通过使用私有静态标志来实现。

js
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

另见