Private properties
Private Eigenschaften sind Gegenstücke zu den regulären Klassen-Eigenschaften, die öffentlich sind, einschließlich Klassenfelder, Klassenmethoden usw. Private Eigenschaften werden durch das Präfix #
erstellt und können außerhalb der Klasse legal nicht referenziert werden. Die Geheimhaltung dieser Klasse-Eigenschaften wird durch JavaScript selbst durchgesetzt. Der einzige Weg, auf eine private Eigenschaft zuzugreifen, ist die Punktnotation, und Sie können dies nur innerhalb der Klasse tun, die die private Eigenschaft definiert.
Private Eigenschaften waren der Sprache vor dieser Syntax nicht eigen. In der prototypischen Vererbung kann ihr Verhalten mit WeakMap
-Objekten oder Closures emuliert werden, aber sie können im Vergleich zur #
-Syntax in Bezug auf Ergonomie nicht mithalten.
Syntax
class ClassWithPrivate {
#privateField;
#privateFieldWithInitializer = 42;
#privateMethod() {
// …
}
static #privateStaticField;
static #privateStaticFieldWithInitializer = 42;
static #privateStaticMethod() {
// …
}
}
Es gibt einige zusätzliche Syntaxeinschränkungen:
- Alle innerhalb einer Klasse deklarierten privaten Bezeichner müssen eindeutig sein. Der Namensraum wird zwischen statischen und Instanzeigenschaften geteilt. Die einzige Ausnahme ist, wenn die beiden Deklarationen ein Getter-Setter-Paar definieren.
- Der private Bezeichner kann nicht
#constructor
sein.
Beschreibung
Die meisten Klasseneigenschaften haben ihre privaten Gegenstücke:
- Private Felder
- Private Methoden
- Private statische Felder
- Private statische Methoden
- Private Getter
- Private Setter
- Private statische Getter
- Private statische Setter
Diese Eigenschaften werden zusammenfassend als private Eigenschaften bezeichnet. Konstruktoren können in JavaScript jedoch nicht privat sein. Um zu verhindern, dass Klassen außerhalb der Klasse konstruiert werden, müssen Sie einen privaten Schalter verwenden.
Private Eigenschaften werden mit # Namen (ausgesprochen „Hash-Namen“) deklariert, das sind Bezeichner mit dem Präfix #
. Das Hash-Präfix ist ein wesentlicher Bestandteil des Eigenschaftennamens – man kann eine Beziehung zur alten Unterstrich-Präfixkonvention _privateField
ziehen – aber es ist keine gewöhnliche Zeichenketteneigenschaft, sodass man nicht dynamisch darauf mit der Klammernnotation zugreifen kann.
Es ist ein Syntaxfehler, auf #
-Namen von außerhalb der Klasse zu verweisen. Es ist auch ein Syntaxfehler, auf private Eigenschaften zu verweisen, die nicht im Klassenkörper deklariert wurden, oder zu versuchen, deklarierte Eigenschaften mit delete
zu entfernen.
class ClassWithPrivateField {
#privateField;
constructor() {
delete this.#privateField; // Syntax error
this.#undeclaredField = 42; // Syntax error
}
}
const instance = new ClassWithPrivateField();
instance.#privateField; // Syntax error
JavaScript, als dynamische Sprache, kann diese Überprüfung zur Kompilierungszeit aufgrund der besonderen Hash-Bezeichner-Syntax durchführen, wodurch es sich auf Syntaxebene von normalen Eigenschaften unterscheidet.
Hinweis: In der Chrome-Konsole ausgeführter Code kann private Eigenschaften außerhalb der Klasse zugreifen. Dies ist eine nur für DevTools geltende Lockerung der JavaScript-Syntaxeinschränkungen.
Wenn Sie von einem Objekt, das die Eigenschaft nicht hat, auf eine private Eigenschaft zugreifen, wird ein TypeError
ausgelöst, anstatt undefined
wie bei normalen Eigenschaften zurückzugeben.
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
Dieses Beispiel veranschaulicht auch, dass Sie auf private Eigenschaften auch innerhalb statischer Funktionen und auf extern definierte Instanzen der Klasse zugreifen können.
Sie können den in
-Operator verwenden, um zu überprüfen, ob ein extern definiertes Objekt eine private Eigenschaft besitzt. Dies gibt true
zurück, wenn das private Feld oder die Methode existiert, und false
, andernfalls.
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"
Beachten Sie eine Folge der Tatsache, dass private Namen immer deklariert und nicht löschbar sind: Wenn Sie feststellen, dass ein Objekt eine private Eigenschaft der aktuellen Klasse besitzt (entweder durch einen try...catch
oder eine in
-Überprüfung), muss es alle anderen privaten Eigenschaften besitzen. Dass ein Objekt die privaten Eigenschaften einer Klasse besitzt, bedeutet im Allgemeinen, dass es von dieser Klasse konstruiert wurde (obwohl nicht immer).
Private Eigenschaften sind nicht Teil des prototypischen Vererbung Modells, da sie nur innerhalb des aktuellen Klassenkörpers zugänglich sind und von Unterklassen nicht geerbt werden. Private Eigenschaften mit demselben Namen innerhalb verschiedener Klassen sind völlig unterschiedlich und interagieren nicht miteinander. Betrachten Sie sie als externe Metadaten, die von der Klasse an jede Instanz angehängt werden. Aus diesem Grund kopiert structuredClone()
keine privaten Eigenschaften, und Object.freeze()
und Object.seal()
haben keine Auswirkungen auf private Eigenschaften.
Für weitere Informationen darüber, wie und wann private Felder initialisiert werden, siehe öffentliche Klassenfelder.
Beispiele
Private Felder
Private Felder umfassen private Instanzfelder und private statische Felder. Private Felder sind nur innerhalb der Klassendeklaration zugänglich.
Private Instanzfelder
Wie ihre öffentlichen Gegenstücke sind private Instanzfelder:
- werden vor dem Ausführen des Konstruktors in einer Basisklasse hinzugefügt oder unmittelbar nachdem
super()
in einer Unterklasse aufgerufen wurde, und - sind nur auf Instanzen der Klasse verfügbar.
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}
Hinweis: #privateField
aus der Basisklasse ClassWithPrivateField
ist privat für ClassWithPrivateField
und von der abgeleiteten Subclass
aus nicht zugänglich.
Rückgabe eines überschreibenden Objekts
Ein Konstruktor einer Klasse kann ein anderes Objekt zurückgeben, das als neues this
für den Konstruktor der abgeleiteten Klasse verwendet wird. Die abgeleitete Klasse kann dann private Felder auf diesem zurückgegebenen Objekt definieren – was bedeutet, dass es möglich ist, private Felder auf unabhängige Objekte zu stempeln.
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
Warnung:
Dies ist eine potenziell sehr verwirrende Sache zu tun. Es wird allgemein geraten, nichts vom Konstruktor zurückzugeben – insbesondere nichts, das nicht mit this
verwandt ist.
Private statische Felder
Wie ihre öffentlichen Gegenstücke sind private statische Felder:
- werden dem Klassenkonstruktor zur Zeitpunkt der Klassenevaluierung hinzugefügt, und
- sind nur auf die Klasse selbst verfügbar.
class ClassWithPrivateStaticField {
static #privateStaticField = 42;
static publicStaticMethod() {
return ClassWithPrivateStaticField.#privateStaticField;
}
}
console.log(ClassWithPrivateStaticField.publicStaticMethod()); // 42
Es gibt eine Einschränkung bei privaten statischen Feldern: Nur die Klasse, die das private statische Feld definiert, kann auf das Feld zugreifen. Dies kann zu unerwartetem Verhalten führen, wenn man this
verwendet. Im folgenden Beispiel bezieht sich this
auf die Subclass
(nicht die ClassWithPrivateStaticField
), wenn wir Subclass.publicStaticMethod()
aufrufen, und verursacht daher einen 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
Das Gleiche gilt, wenn Sie die Methode mit super
aufrufen, da die super
-Methoden nicht mit der Superklasse als this
aufgerufen werden.
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
Es wird empfohlen, immer über den Klassennamen auf private statische Felder zuzugreifen, nicht über this
, damit die Vererbung die Methode nicht unterbricht.
Private Methoden
Private Methoden umfassen private Instanzmethoden und private statische Methoden. Private Methoden sind nur innerhalb der Klassendeklaration zugänglich.
Private Instanzmethoden
Im Gegensatz zu ihren öffentlichen Gegenstücken sind private Instanzmethoden:
- werden unmittelbar installiert, bevor die Instanzfelder installiert werden, und
- sind nur auf Instanzen der Klasse verfügbar, nicht auf deren
.prototype
-Eigenschaft.
class ClassWithPrivateMethod {
#privateMethod() {
return 42;
}
publicMethod() {
return this.#privateMethod();
}
}
const instance = new ClassWithPrivateMethod();
console.log(instance.publicMethod()); // 42
Private Instanzmethoden können Generator-, async- oder async-Generatorfunktionen sein. Private Getter und Setter sind ebenfalls möglich und folgen denselben Syntaxanforderungen wie ihre öffentlichen Getter und Setter Gegenstücke.
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🛑
Im Gegensatz zu öffentlichen Methoden sind private Methoden auf der .prototype
-Eigenschaft ihrer Klasse nicht zugänglich.
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 statische Methoden
Wie ihre öffentlichen Gegenstücke sind private statische Methoden:
- werden dem Klassenkonstruktor zur Zeitpunkt der Klassenevaluierung hinzugefügt, und
- sind nur auf die Klasse selbst verfügbar.
class ClassWithPrivateStaticMethod {
static #privateStaticMethod() {
return 42;
}
static publicStaticMethod() {
return ClassWithPrivateStaticMethod.#privateStaticMethod();
}
}
console.log(ClassWithPrivateStaticMethod.publicStaticMethod()); // 42
Private statische Methoden können Generator-, async- und async-Generatorfunktionen sein.
Die zuvor erwähnte Einschränkung für private statische Felder gilt auch für private statische Methoden und kann ebenfalls zu unerwartetem Verhalten führen, wenn this
verwendet wird. Im folgenden Beispiel bezieht sich this
auf die Subclass
(nicht die ClassWithPrivateStaticMethod
), wenn wir Subclass.publicStaticMethod()
aufrufen, und verursacht daher einen 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
Simulieren privater Konstruktoren
Viele andere Sprachen bieten die Möglichkeit, einen Konstruktor als privat zu markieren, was verhindert, dass die Klasse außerhalb der Klasse selbst instanziiert wird – man kann nur statische Fabrikmethoden verwenden, die Instanzen erstellen, oder überhaupt keine Instanzen erstellen. JavaScript hat keine native Möglichkeit, dies zu tun, aber es kann durch Verwendung eines privaten statischen Schalters erreicht werden.
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 {}
Spezifikationen
Specification |
---|
ECMAScript Language Specification # prod-PrivateIdentifier |
ECMAScript Language Specification # prod-00OK517S |
Browser-Kompatibilität
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
Siehe auch
- Verwendung von Klassen-Leitfaden
- Klassen
- Öffentliche Klassenfelder
class
- Private Syntax FAQ im TC39-Klassenfeldvorschlag
- Die Semantik aller JS-Klassenelemente von Shu-yu Guo (2018)
- Öffentliche und private Klassenfelder auf v8.dev (2018)