Was ist in Typescript das! (Ausrufezeichen / Knall) Operator beim Dereferenzieren eines Mitglieds?

452

Beim Durchsuchen des Quellcodes nach einer tslint-Regel stieß ich auf die folgende Aussage:

if (node.parent!.kind === ts.SyntaxKind.ObjectLiteralExpression) {
    return;
}

Beachten Sie den !Bediener nach node.parent. Interessant!

Ich habe zuerst versucht, die Datei lokal mit meiner aktuell installierten Version von TS (1.5.3) zu kompilieren. Der resultierende Fehler zeigte auf die genaue Position des Knalls:

$ tsc --noImplicitAny memberAccessRule.ts 
noPublicModifierRule.ts(57,24): error TS1005: ')' expected.

Als nächstes habe ich auf den neuesten TS (2.1.6) aktualisiert, der ihn ohne Probleme kompiliert hat. Es scheint also ein Merkmal von TS 2.x zu sein. Aber die Transpilation ignorierte den Knall vollständig, was zu folgendem JS führte:

if (node.parent.kind === ts.SyntaxKind.ObjectLiteralExpression) {
    return;
}

Mein Google Fu hat mich bisher gescheitert.

Was ist der Ausrufezeichenoperator von TS und wie funktioniert er?

Mike Chamberlain
quelle

Antworten:

687

Das ist der Nicht-Null-Assertionsoperator. Auf diese Weise können Sie dem Compiler mitteilen, dass "dieser Ausdruck nicht nulloder nicht undefinedhier sein kann. Beschweren Sie sich also nicht über die Möglichkeit, dass er nulloder ist undefined." Manchmal kann die Typprüfung diese Feststellung nicht selbst treffen.

Es wird hier erklärt :

Ein neuer !Ausdrucksoperator nach dem Fixieren kann verwendet werden, um zu behaupten, dass sein Operand in Kontexten, in denen die Typprüfung diese Tatsache nicht schließen kann, nicht null und nicht undefiniert ist. Insbesondere x!erzeugt die Operation einen Wert vom Typ xmit nullund undefinedausgeschlossen. Ähnlich wie bei Typzusicherungen der Formulare <T>xund x as Twird der !Nicht-Null-Zusicherungsoperator einfach im ausgegebenen JavaScript-Code entfernt.

Ich finde die Verwendung des Begriffs "behaupten" in dieser Erklärung etwas irreführend. Es ist "behaupten" in dem Sinne, dass der Entwickler es behauptet , nicht in dem Sinne, dass ein Test durchgeführt wird. Die letzte Zeile zeigt tatsächlich an, dass kein JavaScript-Code ausgegeben wird.

Louis
quelle
102
Guter Aufruf zur Mehrdeutigkeit.
Estus Flask
8
Gute Erklärung. Ich halte es für eine gute Praxis, die console.assert()betreffende Variable vor dem Anhängen einer nachfolgenden Variablen zu bearbeiten !. Da add !den Compiler anweist, die Nullprüfung zu ignorieren, wird es in Javascript zu noop kompiliert. Wenn Sie also nicht sicher sind, ob die Variable nicht null ist, führen Sie besser eine explizite Assert-Prüfung durch.
Jayesh
12
Als motivierendes Beispiel: Die Verwendung des neuen ES Map-Typs mit Code wie dict.has(key) ? dict.get(key) : 'default';dem TS-Compiler kann nicht darauf schließen, dass der getAufruf niemals null / undefiniert zurückgibt. dict.has(key) ? dict.get(key)! : 'default';schränkt den Typ korrekt ein.
Kitsu.eb
1
Gibt es einen Slang für diesen Operator, wie sich der Elvis-Operator auf den binären Operator bezieht?
Ebakunin
@ Jayesh, könnten Sie die bewährte Methode console.assert () erweitern, könnten Sie ein Beispiel posten?
Christopher Francisco
167

Louis 'Antwort ist großartig, aber ich dachte, ich würde versuchen, es kurz und bündig zusammenzufassen:

Der Bang-Operator weist den Compiler an, die Einschränkung "nicht null", die er sonst möglicherweise verlangt, vorübergehend zu lockern. Dem Compiler heißt es: "Als Entwickler weiß ich besser als Sie, dass diese Variable momentan nicht null sein kann."

Mike Chamberlain
quelle
85
Dann haben Sie als Entwickler es vermasselt.
Mike Chamberlain
8
Oder als Compiler hat es versagt. Wenn der Konstruktor eine Eigenschaft nicht initialisiert, ein Lifecycle-Hook dies jedoch tut und der Compiler dies nicht erkennt.
Mukus
26
Dies liegt nicht in der Verantwortung des TS-Compilers. Im Gegensatz zu einigen anderen Sprachen (z. B. C #) verlangt JS (und damit TS) nicht, dass Variablen vor der Verwendung initialisiert werden. Oder anders ausgedrückt: In JS werden alle Variablen mit JS deklariert varoder letimplizit mit initialisiert undefined. Darüber hinaus können Klasseninstanzeigenschaften als solche deklariert werden, class C { constructor() { this.myVar = undefined; } }was vollkommen legal ist. Schließlich sind Lifecycle-Hooks vom Framework abhängig. Zum Beispiel implementieren Angular und React sie unterschiedlich. Es ist daher nicht zu erwarten, dass der TS-Compiler darüber nachdenkt.
Mike Chamberlain
1
Gibt es einen gültigen Anwendungsfall für den Bang-Operator, der die Attraktivität der auf Kontrollfluss basierenden Typanalyse in TS berücksichtigt?
Eugene Karataev
@EugeneKarataev Ja, Frameworks initialisieren häufig Variablen in sich selbst und die ts-Kontrollflussanalyse kann sie nicht erfassen. Die Nutzung ist sicherlich reduziert, aber Sie werden auf Fälle stoßen, in denen Sie sie benötigen.
arg20