Start · Sprachen · JavaScript · Referenz · Private Elemente

Private Elemente

Keyword

Private Elemente (mit Präfix <code>#</code>) sind Felder, Methoden und Accessoren einer Klasse, auf die nur innerhalb des Klassenkörpers zugegriffen werden kann.

seit JavaScript ES2022 (private Felder, Methoden, Accessoren); #x Kategorie: syntax

Signatur

class Klasse { #feld = wert; #methode() {} static #statischesFeld; get #accessor() {} }

Beschreibung

Private Elemente sind die gekapselten Gegenstücke zu den öffentlichen Klassen-Elementen. Man deklariert sie mit einem vorangestellten #, z. B. #count oder #validate(). Das gibt es für Instanzfelder, Instanzmethoden, Getter/Setter und für alle drei auch als static-Variante. Die Sprache selbst setzt diese Privatsphäre durch („hard private“). Anders als bei der Konvention mit einem Unterstrich (_count) kommt man von außerhalb des Klassenkörpers weder lesend noch schreibend an diese Elemente heran.

Ein Zugriff von außen wie obj.#count ist ein Syntaxfehler, der schon beim Parsen auffällt. Greift man innerhalb der Klasse auf ein privates Element eines Objekts zu, das es nicht besitzt, wird ein TypeError geworfen. Mit der Prüfung #feld in obj (ergonomische Markenprüfung, engl. „brand check“) lässt sich sicher testen, ob ein Objekt von der eigenen Klasse erzeugt wurde.

Private Elemente eignen sich überall dort, wo interner Zustand oder Hilfslogik vor Nutzern einer Klasse verborgen werden soll: Invarianten schützen (z. B. Kontostand nur über Methoden änderbar), Implementierungsdetails ohne Breaking Changes austauschen, Namenskollisionen mit Unterklassen vermeiden. Private Namen sind pro Klassendefinition eindeutig. Eine Unterklasse kann ein gleichnamiges #feld deklarieren, ohne das der Oberklasse zu berühren, und kann auf die privaten Elemente der Oberklasse auch nicht zugreifen.

Private Elemente sind keine Eigenschaften im üblichen Sinn. Sie tauchen weder in Object.keys(), JSON.stringify() und Reflect.ownKeys() noch beim Spread-Operator auf, und Proxy-Traps sehen sie nicht.

Parameter

Name Typ Default Beschreibung
$#name Pflicht PrivateIdentifier Name des privaten Elements, immer mit #-Präfix. Das # gehört fest zum Namen: #a und a sind zwei verschiedene Elemente.
$wert any undefined Optionaler Initialisierer eines privaten Feldes. Er wird bei jeder Instanziierung ausgewertet (bei static einmal beim Auswerten der Klasse).
$static Modifikator Macht das private Element zu einem Element der Klasse selbst statt der Instanzen, z. B. static #instances = 0.

Rückgabewert

Typ
any
Beschreibung
Ein Zugriff auf this.#name liefert den Wert des Feldes, die Methode bzw. das Ergebnis des Getters. #name in obj liefert einen boolean.

Beispiele

Gekapselter Kontostand mit privatem Feld und privater Methode

class Konto {
  #saldo = 0;

  #pruefeBetrag(betrag) {
    if (!Number.isFinite(betrag) || betrag <= 0) {
      throw new RangeError('Ungültiger Betrag');
    }
  }

  einzahlen(betrag) {
    this.#pruefeBetrag(betrag);
    this.#saldo += betrag;
    return this;
  }

  get saldo() {
    return this.#saldo;
  }
}

const konto = new Konto().einzahlen(100).einzahlen(50);
console.log(konto.saldo);
console.log(Object.keys(konto));
console.log(JSON.stringify(konto));
// konto.#saldo = 1e6; // SyntaxError: Private field '#saldo' must be declared in an enclosing class
150 [] {}

Statische private Felder: Instanzzähler und Singleton

class Config {
  static #instanz = null;
  static #erzeugt = 0;
  #werte;

  constructor(werte) {
    this.#werte = { ...werte };
    Config.#erzeugt++;
  }

  static holen() {
    Config.#instanz ??= new Config({ theme: 'dark' });
    return Config.#instanz;
  }

  static get anzahl() {
    return Config.#erzeugt;
  }

  get(key) {
    return this.#werte[key];
  }
}

const a = Config.holen();
const b = Config.holen();
console.log(a === b, a.get('theme'), Config.anzahl);
true dark 1

Markenprüfung mit #feld in obj

class Punkt {
  #x;
  #y;
  constructor(x, y) {
    this.#x = x;
    this.#y = y;
  }

  static istPunkt(obj) {
    return #x in obj;
  }

  equals(other) {
    if (!Punkt.istPunkt(other)) return false;
    return this.#x === other.#x && this.#y === other.#y;
  }
}

const p = new Punkt(1, 2);
console.log(p.equals(new Punkt(1, 2)));
console.log(p.equals({ x: 1, y: 2 }));
console.log(Punkt.istPunkt(p));
true false true

Privater Accessor mit Validierung

class Temperatur {
  #celsius = 0;

  get #kelvin() {
    return this.#celsius + 273.15;
  }

  set #wert(c) {
    if (c < -273.15) throw new RangeError('Unter dem absoluten Nullpunkt');
    this.#celsius = c;
  }

  setzen(c) {
    this.#wert = c;
    return `${c} °C = ${this.#kelvin.toFixed(2)} K`;
  }
}

console.log(new Temperatur().setzen(25));
25 °C = 298.15 K

// Wichtig · Fallstricke

  • Deklarationspflicht: Jedes private Element muss im Klassenkörper deklariert sein. this.#neu = 1 ohne vorherige Deklaration ist ein SyntaxError. Private Felder lassen sich nicht dynamisch anlegen und nicht mit delete entfernen.
  • Kein dynamischer Zugriff: this['#feld'] spricht eine ganz normale öffentliche Eigenschaft namens "#feld" an, nicht das private Element. Eine Schreibweise wie this[#feld] gibt es nicht.
  • TypeError bei fremden Objekten: Wird eine Methode mit fremdem this aufgerufen, etwa über call oder bei einem losgelösten Callback, wirft this.#x einen TypeError. Hier helfen #x in obj oder bind.
  • Proxy: Auf einem Proxy um eine Instanz schlägt der Zugriff auf private Elemente fehl, denn der Proxy ist nicht das Zielobjekt. Das betrifft z. B. Reaktivitätssysteme wie Vue.
  • Statische private Elemente und Vererbung: this.#statisch in einer statischen Methode wirft einen TypeError, wenn die Methode über eine Unterklasse aufgerufen wird. Deshalb besser den Klassennamen verwenden (Config.#instanz).
  • Keine Kapselung pro Instanz: Privat heißt privat pro Klasse. Methoden einer Klasse dürfen auf other.#x anderer Instanzen derselben Klasse zugreifen.
  • Kompatibilität: Private Felder, Methoden und Accessoren funktionieren in allen modernen Browsern (Chrome 84+, Firefox 90+, Safari 15+) und ab Node.js 14.6. #x in obj braucht Chrome 91+, Firefox 90+, Safari 15+ bzw. Node.js 16.4+. Für ältere Umgebungen übersetzen Babel oder TypeScript den Code, meist mit WeakMap.
  • Das TypeScript-Schlüsselwort private wirkt nur zur Übersetzungszeit. Echte Privatsphäre zur Laufzeit bietet nur #.

Siehe auch