TypeScript hat drei Zugriffsmodifikatoren für Klassenmember: public (der Standard), protected und private. Sie steuern, wo ein Member verwendet werden darf, und der Compiler meldet jeden Zugriff von der falschen Stelle.
Die letzte Zeile ist ein Compilerfehler, aber sieh dir an, was sie ausgegeben hat, als sie trotzdem lief: 123-45-6789. Das ist die wichtigste Tatsache auf dieser Seite, und der Abschnitt zu private erklärt sie.
Was jeder Modifikator erlaubt
| Modifikator | In der Klasse | In einer Unterklasse | Von außen | Zur Laufzeit durchgesetzt |
|---|---|---|---|---|
public (Standard) | ja | ja | ja | nichts durchzusetzen |
protected | ja | ja | nein | nein |
private | ja | nein | nein | nein |
#name (JavaScript) | ja | nein | nein | ja |
readonly | lesen, und schreiben im Konstruktor | lesen | lesen | nein |
Die Modifikatoren wirken auf Felder, Methoden, Getter und Setter, Konstruktoren und Parameter Properties (constructor(private id: string)). public zu schreiben ist optional, und viele Codebasen lassen es weg.
private ist eine Prüfung beim Kompilieren
TypeScript entfernt private zusammen mit den übrigen Typen. Die kompilierte Klasse hat eine gewöhnliche Eigenschaft, also sieht alles sie, was nicht durch die Typprüfung geht: Aufrufer in reinem JavaScript, JSON.stringify, Object.keys und sogar die eckigen Klammern von TypeScript selbst, die als bewusster Notausgang erlaubt sind.
Für den Zweck von private ist das in Ordnung: anderen Entwicklern (und deinem Editor) mitzuteilen, dass ein Member ein Implementierungsdetail ist. Es ist keine Sicherheitsgrenze, und ein private Feld landet in Logs und JSON-Antworten, wenn du es nicht selbst entfernst.
#private-Felder: zur Laufzeit durchgesetzt
JavaScript hat eigene private Felder, geschrieben mit #. Die Engine setzt sie durch: Außerhalb der Klasse ist obj.#field nicht einmal gültige Syntax, und das Feld taucht weder in Object.keys noch in JSON.stringify oder console.log auf.
s.#token außerhalb der Klasse zu schreiben ist der Fehler TS18013 (Property '#token' is not accessible outside class 'Session' because it has a private identifier), und anders als bei private gibt es auch zur Laufzeit keinen Weg daran vorbei. Die Laufzeitregeln stehen unter private Felder in JavaScript.
private vs #private: was verwenden
private x | #x | |
|---|---|---|
| Geprüft von | dem Compiler | der JavaScript-Engine |
Sichtbar für Object.keys / JSON.stringify | ja | nein |
Zugriff mit eckigen Klammern obj["x"] | erlaubt | nicht möglich |
Unterklasse kann ein eigenes x deklarieren | nein, es kollidiert | ja, jede Klasse hat ihr eigenes #x |
Von einem Spread { ...obj } kopiert | ja | nein |
| Syntax bei Methoden | private helper() | #helper() |
Nimm #private, wenn die Daten zur Laufzeit privat bleiben müssen (Tokens, interner Zustand, den Nutzer einer Bibliothek nicht anfassen dürfen) oder wenn JSON.stringify sie auslassen soll. Nimm private, wenn es nur um eine saubere öffentliche API geht, wenn ein Framework das Feld per Reflection lesen muss oder um zum bestehenden Stil einer Codebasis zu passen. Kombiniere sie nicht: private #x ist der Fehler TS18010 (An accessibility modifier cannot be used with a private identifier).
protected und Unterklassen
Ein protected Member ist in der Klasse und in jeder Klasse verfügbar, die sie erweitert, aber nicht auf Instanzen von außen.
Eine Regel überrascht viele. In Polygon kannst du sides auf this oder auf einem anderen Polygon lesen, aber nicht auf einem einfachen Shape, das als Parameter übergeben wird: other.sides mit other: Shape ist der Fehler TS2446 (Property 'sides' is protected and only accessible through an instance of class 'Polygon'. This is an instance of class 'Shape'.). Eine Unterklasse darf nur die protected Member von Objekten erreichen, die zu ihrem eigenen Zweig der Hierarchie gehören.
Eine Unterklasse kann einen protected Member durch erneute Deklaration öffentlich machen, aber keinen öffentlichen Member zu protected oder private machen. Deklariere ihn mit Initialisierer neu (public override sides = 6) oder nur als Typ (declare public sides: number). Ein nacktes public sides: number; wird mit TS2564 und TS2612 abgelehnt, weil es mit modernen Klassenfeldern den geerbten Wert nach dem Ende von super() auf undefined zurücksetzen würde.
readonly
readonly verhindert die Neuzuweisung nach der Konstruktion. Das Feld kann in seiner Deklaration oder im Konstruktor gesetzt werden; jede spätere Zuweisung ist der Fehler TS2540.
Hier zeigen sich zwei Grenzen. readonly ist flach: Das Array lässt sich nicht ersetzen, aber sein Inhalt kann sich ändern (typisiere es als readonly string[], um push zu verhindern). Und wie private verschwindet es zur Laufzeit, die Zuweisung, die der Compiler abgelehnt hat, lief also trotzdem. Für einen Wert, der sich zur Laufzeit nicht ändern darf, nimm Object.freeze oder einen Getter ohne Setter. Die Seite zu readonly behandelt Readonly<T> und readonly Arrays.
readonly lässt sich mit den Zugriffsmodifikatoren kombinieren: private readonly cache = new Map<string, number>() ist ein verbreitetes Muster für ein Feld, das intern ist und nie neu zugewiesen wird.
Häufige Fehler
privateals Sicherheit behandeln. Zur Laufzeit ist es weg. Nimm#fieldfür Daten, die verborgen bleiben müssen, und gib ein Objekt mit Geheimnissen nie direkt anJSON.stringify.- Überall
publicschreiben. Es ist der Standard; es hinzuzufügen ändert nichts. protectedfür alles „Interne“ verwenden. Braucht keine Unterklasse den Member, hältprivatedie Oberfläche kleiner.- Erwarten, dass
readonlyverschachtelte Daten einfriert. Es verhindert nur die Neuzuweisung der Eigenschaft selbst.
Häufig gestellte Fragen
Welche Zugriffsmodifikatoren gibt es in TypeScript?
public (der Standard: überall zugänglich), protected (in der Klasse und ihren Unterklassen) und private (nur in der Klasse). readonly ist ein eigener Modifikator, der die Neuzuweisung nach dem Konstruktor verhindert und sich mit jedem der drei kombinieren lässt.
Was ist der Unterschied zwischen private und #private in TypeScript?
private prüft nur der Compiler; das erzeugte JavaScript hat eine gewöhnliche Eigenschaft, also sehen obj["secret"], JSON.stringify und Object.keys sie weiterhin. #secret ist ein privates Feld von JavaScript: Die Laufzeit setzt es durch, und Code außerhalb der Klasse kann es überhaupt nicht lesen.
Ist private in TypeScript wirklich privat?
Nur beim Kompilieren. Danach ist das Feld eine normale Eigenschaft, die jeder JavaScript-Code lesen kann. TypeScript erlaubt sogar den Zugriff mit eckigen Klammern (obj["field"]) auf private Member als Notausgang. Nimm #field, wenn die Privatheit zur Laufzeit gelten muss.
Was ist der Unterschied zwischen protected und private in TypeScript?
Ein private Member ist nur in der Klasse sichtbar, die ihn deklariert. Ein protected Member ist auch in Unterklassen sichtbar. Auf keinen der beiden kann man von außerhalb der Klassenhierarchie über eine Instanz zugreifen.
Können readonly Eigenschaften in TypeScript geändert werden?
Eine readonly Eigenschaft kann in ihrer Deklaration oder im Konstruktor zugewiesen werden und sonst nirgends (andernfalls TS2540). Sie ist flach: Ein readonly tags: string[] lässt sich nicht ersetzen, aber tags.push() funktioniert weiterhin. Nimm readonly string[], um auch das zu verhindern.