私有属性

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

¥Private properties are counterparts of the regular class properties which are public, including class fields, class methods, etc. Private properties get created by using a hash # prefix and cannot be legally referenced outside of the class. The privacy encapsulation of these class properties is enforced by JavaScript itself. The only way to access a private property is via dot notation, and you can only do so within the class that defines the private property.

在这种语法存在之前,私有属性并不是该语言的原生。在原型继承中,它的行为可以用 WeakMap 对象或 closures 来模拟,但在人机工程学方面它们无法与 # 语法相比。

¥Private properties were not native to the language before this syntax existed. In prototypal inheritance, its behavior may be emulated with WeakMap objects or closures, but they can't compare to the # syntax in terms of ergonomics.

语法

¥Syntax

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

  #privateMethod() {
    // …
  }

  static #privateStaticField;
  static #privateStaticFieldWithInitializer = 42;

  static #privateStaticMethod() {
    // …
  }
}

还有一些额外的语法限制:

¥There are some additional syntax restrictions:

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

描述

¥Description

大多数类属性都有其私有对应项:

¥Most class properties have their private counterparts:

  • 私有字段
  • 私有方法
  • 私有静态字段
  • 私有静态方法
  • 私有获取器
  • 私有二传手
  • 私有静态获取器
  • 私有静态设置器

这些功能统称为私有属性。但是,constructors 在 JavaScript 中不能是私有的。为了防止在类之外构造类,你必须 使用私有标志

¥These features are collectively called private properties. However, constructors cannot be private in JavaScript. To prevent classes from being constructed outside of the class, you have to use a private flag.

私有属性用 # 名称(发音为 "哈希名称")声明,这些名称是以 # 为前缀的标识符。哈希前缀是属性名称的固有部分 - 你可以与旧的下划线前缀约定 _privateField 建立关系 - 但它不是普通的字符串属性,因此你无法使用 括号表示法 动态访问它。

¥Private properties are declared with # names (pronounced "hash names"), which are identifiers prefixed with #. The hash prefix is an inherent part of the property name — you can draw relationship with the old underscore prefix convention _privateField — but it's not an ordinary string property, so you can't dynamically access it with the bracket notation.

从类外部引用 # 名称是一个语法错误。引用未在类主体中声明的私有属性,或尝试删除使用 delete 声明的属性也是语法错误。

¥It is a syntax error to refer to # names from outside of the class. It is also a syntax error to refer to private properties that were not declared in the class body, or to attempt to remove declared properties with 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 作为一种动态语言,由于特殊的哈希标识符语法而能够执行这种编译时检查,使其在语法级别上不同于普通属性。

¥JavaScript, being a dynamic language, is able to perform this compile-time check because of the special hash identifier syntax, making it different from normal properties on the syntax level.

注意:在 Chrome 控制台中运行的代码可以访问类外部的私有属性。这是对 JavaScript 语法限制的仅限 DevTools 的放宽。

¥Note: Code run in the Chrome console can access private properties outside the class. This is a DevTools-only relaxation of the JavaScript syntax restriction.

如果从不具有该属性的对象访问私有属性,则会抛出 TypeError,而不是像普通属性那样返回 undefined

¥If you access a private property from an object that doesn't have the property, a TypeError is thrown, instead of returning undefined as normal properties do.

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

此示例还说明你也可以访问静态函数内以及外部定义的类实例上的私有属性。

¥This example also illustrates that you can access private properties within static functions too, and on externally defined instances of the class.

你可以使用 in 运算符来检查外部定义的对象是否拥有私有属性。如果私有字段或方法存在,则返回 true,否则返回 false

¥You can use the in operator to check whether an externally defined object possesses a private property. This will return true if the private field or method exists, and false otherwise.

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...catchin 检查),则它必须拥有所有其他私有属性。拥有类的私有属性的对象通常意味着它是由该类构造的(尽管是 不总是)。

¥Note a corollary of private names being always pre-declared and non-deletable: if you found that an object possesses one private property of the current class (either from a try...catch or an in check), it must possess all other private properties. An object possessing the private properties of a class generally means it was constructed by that class (although not always).

私有属性不是 原型继承 模型的一部分,因为它们只能在当前类的主体内访问,并且不能由子类继承。不同类中具有相同名称的私有属性完全不同,并且不能互操作。将它们视为附加到每个实例的外部元数据,由类管理。因此,structuredClone() 不会克隆私有属性,而 Object.freeze()Object.seal() 对私有属性没有影响。

¥Private properties are not part of the prototypical inheritance model since they can only be accessed within the current class's body and aren't inherited by subclasses. Private properties with the same name within different classes are entirely different and do not interoperate with each other. See them as external metadata attached to each instance, managed by the class. For this reason, structuredClone() does not clone private properties, and Object.freeze() and Object.seal() have no effect on private properties.

有关如何以及何时初始化私有字段的更多信息,请参阅 公共类字段

¥For more information on how and when private fields are initialized, see public class fields.

示例

¥Examples

私有字段

¥Private fields

私有字段包括私有实例字段和私有静态字段。私有字段只能从类声明内部访问。

¥Private fields include private instance fields and private static fields. Private fields are only accessible from inside the class declaration.

私有实例字段

¥Private instance fields

与公共对应字段一样,私有实例字段:

¥Like their public counterparts, private instance fields:

  • 在基类中的构造函数运行之前添加,或者在子类中调用 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 访问。

¥Note: #privateField from the ClassWithPrivateField base class is private to ClassWithPrivateField and is not accessible from the derived Subclass.

返回覆盖对象

¥Returning overriding object

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

¥A class's constructor can return a different object, which will be used as the new this for the derived class constructor. The derived class may then define private fields on that returned object — meaning it is possible to "stamp" private fields onto unrelated objects.

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 properties twice
new Stamper(obj); // Error: Initializing an object twice is an error with private fields

警告:这可能是一件非常令人困惑的事情。通常建议你避免从构造函数返回任何内容 - 尤其是与 this 无关的内容。

¥Warning: This is a potentially very confusing thing to do. You are generally advised to avoid returning anything from the constructor — especially something unrelated to this.

私有静态字段

¥Private static fields

与公共静态字段一样,私有静态字段:

¥Like their public counterparts, private static fields:

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

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

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

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

¥There is a restriction on private static fields: only the class which defines the private static field can access the field. This can lead to unexpected behavior when using this. In the following example, this refers to the Subclass class (not the ClassWithPrivateStaticField class) when we try to call Subclass.publicStaticMethod(), and so causes a 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 来调用

¥This is the same if you call the method with super, because super methods are not called with the super class as 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,这样继承就不会破坏该方法。

¥You are advised to always access private static fields through the class name, not through this, so inheritance doesn't break the method.

私有方法

¥Private methods

私有方法包括私有实例方法和私有静态方法。私有方法只能从类声明内部访问。

¥Private methods include private instance methods and private static methods. Private methods are only accessible from inside the class declaration.

私有实例方法

¥Private instance methods

与公共方法不同,私有实例方法:

¥Unlike their public counterparts, private instance methods:

  • 在安装实例字段之前立即安装,并且
  • 仅适用于该类的实例,不适用于其 .prototype 属性。
js
class ClassWithPrivateMethod {
  #privateMethod() {
    return 42;
  }

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

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

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

¥Private instance methods may be generator, async, or async generator functions. Private getters and setters are also possible, and follow the same syntax requirements as their public getter and setter counterparts.

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

¥Unlike public methods, private methods are not accessible on the .prototype property of their class.

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

私有静态方法

¥Private static methods

与公共方法一样,私有静态方法:

¥Like their public counterparts, private static methods:

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

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

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

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

¥Private static methods may be generator, async, and async generator functions.

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

¥The same restriction previously mentioned for private static fields holds for private static methods, and similarly can lead to unexpected behavior when using this. In the following example, when we try to call Subclass.publicStaticMethod(), this refers to the Subclass class (not the ClassWithPrivateStaticMethod class) and so causes a 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

模拟私有构造函数

¥Simulating private constructors

许多其他语言都包含将构造函数标记为私有的功能,这可以防止类在类本身之外实例化 - 你只能使用创建实例的静态工厂方法,或者根本无法创建实例。JavaScript 没有本地方法来执行此操作,但可以通过使用私有静态标志来完成。

¥Many other languages include the capability to mark a constructor as private, which prevents the class from being instantiated outside of the class itself — you can only use static factory methods that create instances, or not be able to create instances at all. JavaScript does not have a native way to do this, but it can be accomplished by using a private static flag.

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 {}

规范

Specification
ECMAScript Language Specification
# prod-PrivateIdentifier
ECMAScript Language Specification
# prod-00OK517S

¥Specifications

浏览器兼容性

¥Browser compatibility

javascript.classes.private_class_fields

BCD tables only load in the browser

javascript.classes.private_class_fields_in

BCD tables only load in the browser

javascript.classes.private_class_methods

BCD tables only load in the browser

也可以看看

¥See also