Ein Ausrufezeichen hinter einem Ausdruck, value!, ist der Non-Null-Assertion-Operator. Er entfernt null und undefined aus dem Typ, sodass ein number | undefined als number verwendet werden kann. Er ist ein Versprechen an den Compiler, keine Prüfung: Zur Laufzeit passiert nichts.
Ohne das ! ist tea.toFixed(2) der Fehler TS18048, 'tea' is possibly 'undefined'. Mit ihm kompiliert der Code, weil du dem Compiler gesagt hast, dass der Schlüssel existiert.
Was x! zur Laufzeit tut: nichts
Das ! wird aus der Ausgabe entfernt. Das kompilierte JavaScript für prices.get("coffee")! ist einfach prices.get("coffee"). Ist die Assertion falsch, tritt der Fehler später auf, an der ersten Stelle, an der der fehlende Wert verwendet wird:
Das Programm gibt Cannot read properties of undefined (reading 'toFixed') aus. Der Absturz passiert in der Zeile nach dem !, und in echtem Code kann er viel weiter entfernt sein: Das undefined kann in einem Objekt gespeichert werden und in einer anderen Datei explodieren. Diese Entfernung macht ! riskant.
Definite Assignment: let x!: T
Dasselbe Zeichen in einer Deklaration bedeutet etwas Verwandtes. TypeScript verfolgt, ob eine Variable zugewiesen wird, bevor sie gelesen wird, und kann einer Zuweisung in einer anderen Funktion nicht folgen:
index.ts(9,13): error TS2454: Variable 'config' is used before being assigned.
let config!: { port: number }; ist eine Definite Assignment Assertion: „Das wird vor jedem Lesen zugewiesen.“ Sie behebt den Fehler, mit demselben Haken wie x!: Wird init() jemals übersprungen, liest das Lesen undefined. Umstrukturieren ist meist besser, zum Beispiel const config = init();, wobei init das Objekt zurückgibt.
Klasseneigenschaften: prop!: T
Mit strictPropertyInitialization (Teil von strict) muss jede Klasseneigenschaft in ihrer Deklaration oder im Konstruktor initialisiert werden. Sonst bekommst du TS2564: Property 'socket' has no initializer and is not definitely assigned in the constructor. Wird eine Eigenschaft später von einer Methode oder einem Framework gesetzt, sagt prop!: T dem Compiler, dass er sie akzeptieren soll:
Das erste console.log zeigt die Lücke: Der Typ sagt, socket sei immer da, aber vor open() ist es undefined. c.socket.send an dieser Stelle aufzurufen kompiliert und wirft. Kann die Eigenschaft wirklich fehlen, deklariere sie als socket?: ... und prüfe sie, oder erzeuge das Objekt im Konstruktor. Der Hauptort, an dem prop! gängige Praxis ist, ist ein Framework, das eine Eigenschaft nach der Konstruktion füllt: @ViewChild(...) child!: ChildDirective in Angular (gesetzt, bevor ngAfterViewInit läuft), oder Entity-Klassen von ORMs, deren Spalten die Bibliothek beim Laden einer Zeile füllt (die Dokumentation von MikroORM schreibt @Property() title!: string).
Sicherere Alternativen
Die meisten ! lassen sich durch etwas ersetzen, das der Compiler prüft, oder durch eine Prüfung, die an der richtigen Stelle laut scheitert:
Beide Hilfsfunktionen engen den Typ ein wie !, aber eine falsche Annahme erzeugt missing HOST direkt an Ort und Stelle statt eines TypeError irgendwo anders. assertDefined ist eine Assertion-Funktion (asserts value is ...): Nach dem Aufruf behandelt der Compiler host als string. Weitere Muster unter Type Guards.
| Statt | Schreibe | Was passiert, wenn der Wert fehlt |
|---|---|---|
user!.name | if (user) { user.name } | der Block wird übersprungen |
user!.name | user?.name | undefined |
count! | count ?? 0 | der Standardwert wird verwendet |
map.get(k)! | must(map.get(k), "k") | ein klarer Fehler in dieser Zeile |
let x!: T | const x = compute() | nichts kann schiefgehen |
Die anderen Ausrufezeichen
! bedeutet je nach Position Verschiedenes:
| Code | Bedeutung |
|---|---|
value! (hinter einem Ausdruck) | Non-Null-Assertion, nur TypeScript |
let x!: T, prop!: T | Definite Assignment Assertion, nur TypeScript |
!value (vor einem Ausdruck) | logisches NICHT, reines JavaScript |
!!value | wandelt in einen Boolean um, reines JavaScript |
a !== b, a != b | Ungleichheit, reines JavaScript |
Nur die ersten beiden werden beim Kompilieren entfernt. !value und !!value laufen zur Laufzeit und geben einen Boolean zurück.
Häufig gestellte Fragen
Was bedeutet ein Ausrufezeichen hinter einer Variablen in TypeScript?
value! ist der Non-Null-Assertion-Operator. Er sagt dem Compiler, dass value nicht null oder undefined ist, also verliert sein Typ diese beiden Member: string | undefined wird zu string. Er wird aus dem erzeugten JavaScript entfernt und fügt keine Prüfung zur Laufzeit hinzu; liegst du falsch, scheitert das Programm später mit einem TypeError.
Was ist der Unterschied zwischen ! und ? in TypeScript?
x! behauptet, dass der Wert vorhanden ist, und gibt dir den Typ ohne null, ohne Prüfung. x?.y prüft zur Laufzeit: Ist x null oder undefined, hört es auf und gibt undefined zurück. In einer Deklaration macht name?: string eine Eigenschaft optional, während name!: string sagt, dass eine Pflichteigenschaft irgendwo zugewiesen wird, wo der Compiler es nicht sehen kann.
Was bedeutet let x!: string?
Das ist eine Definite Assignment Assertion. Sie sagt dem Compiler, dass die Variable zugewiesen wird, bevor sie gelesen wird, auch wenn der Compiler das nicht beweisen kann (etwa weil die Zuweisung in einer anderen Funktion passiert). Ohne sie ist das Lesen der Variablen der Fehler TS2454, Variable 'x' is used before being assigned.
Wie behebe ich "has no initializer and is not definitely assigned in the constructor"?
Das ist der Fehler TS2564 aus strictPropertyInitialization. Gib der Eigenschaft einen Startwert, weise sie im Konstruktor zu, mach sie optional (prop?: T) oder, wenn ein Framework oder eine Init-Methode sie wirklich vor der Verwendung setzt, schreibe prop!: T. Das ! ist die letzte Option, weil nichts das Versprechen prüft.
Ist der Non-Null-Assertion-Operator schlechte Praxis?
Er ist nicht falsch, aber jedes ! ist eine ungeprüfte Behauptung. Lint-Setups wie @typescript-eslint/no-non-null-assertion markieren ihn. Bevorzuge eine Prüfung, die einengt (if (x), x ?? fallback, x?.y), oder eine Hilfsfunktion, die einen klaren Fehler wirft. Behalte ! für Stellen, an denen der Wert durch Logik garantiert ist, der der Compiler nicht folgen kann.