Co to jest w maszynopisie! (wykrzyknik / huk) operator przy dereferencji członka?

453

Przeglądając kod źródłowy reguły tslint, natrafiłem na następujące oświadczenie:

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

Zauważ !operatora po node.parent. Ciekawy!

Najpierw próbowałem skompilować plik lokalnie z moją aktualnie zainstalowaną wersją TS (1.5.3). Wynikający błąd wskazał dokładną lokalizację huku:

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

Następnie zaktualizowałem do najnowszej wersji TS (2.1.6), która skompilowała go bez problemu. Wydaje się, że jest to cecha TS 2.x. Ale transpilacja całkowicie zignorowała huk, co spowodowało następujący JS:

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

Moje Google fu do tej pory mnie zawiodło.

Co to jest operator wykrzyknika TS i jak działa?

Mike Chamberlain
źródło

Odpowiedzi:

691

To jest operator asercji niepustej. Jest to sposób, by powiedzieć kompilatorowi, że „tego wyrażenia nie może być nullani undefinedtutaj, więc nie narzekaj na możliwość, że jest ono nulllub undefined”. Czasami moduł sprawdzania typu nie jest w stanie samodzielnie dokonać tego ustalenia.

Wyjaśniono to tutaj :

!Można użyć nowego operatora wyrażeń po poprawce, aby stwierdzić, że jego operand nie jest pusty i nie jest zdefiniowany w kontekstach, w których moduł sprawdzania typu nie jest w stanie stwierdzić tego faktu. W szczególności operacja x!generuje wartość typu xz nulli undefinedwykluczone. Podobnego do typu twierdzenia formy <T>xi x as T, The !operatora twierdzenie niezerowe po prostu usunięte w emitowanych kodu JavaScript.

Uważam, że użycie terminu „twierdzić” jest nieco mylące w tym wyjaśnieniu. Jest to „stwierdzenie” w tym sensie, że programista to potwierdza , a nie w tym sensie, że zostanie przeprowadzony test. Ostatni wiersz rzeczywiście wskazuje, że nie powoduje emisji kodu JavaScript.

Louis
źródło
102
Dobre wezwanie do „potwierdzenia” dwuznaczności.
Estus Flask
8
Dobre wytłumaczenie. Uważam, że dobrą praktyką jest wykonanie console.assert()danej zmiennej przed dodaniem jej !po niej. Ponieważ add !mówi kompilatorowi, aby zignorował kontrolę zerową, kompiluje się do noop w javascript. Więc jeśli nie masz pewności, że zmienna ma wartość inną niż null, lepiej wykonaj jawną kontrolę potwierdzenia.
Jayesh
12
Jako motywujący przykład: użycie nowego typu ES Map z kodem takim jak dict.has(key) ? dict.get(key) : 'default';kompilator TS nie może wywnioskować, że getwywołanie nigdy nie zwraca wartości null / undefined. dict.has(key) ? dict.get(key)! : 'default';zawęża typ poprawnie.
kitsu.eb
1
Czy jest jakiś slang dla tego operatora, na przykład jak Elvis odnosi się do operatora binarnego?
ebakunin
@Jayesh mógłbyś rozwinąć na dobrej praktyce console.assert (), czy mógłbyś zamieścić przykład?
Christopher Francisco
168

Odpowiedź Louisa jest świetna, ale pomyślałem, że spróbuję to podsumować zwięźle:

Operator Bang nakazuje kompilatorowi tymczasowe złagodzenie ograniczenia „nie zerowego”, którego w przeciwnym razie mógłby wymagać. Mówi do kompilatora: „Jako programista wiem lepiej od ciebie, że ta zmienna nie może być teraz zerowa”.

Mike Chamberlain
źródło
85
Następnie, jako programista, popełniłeś błąd.
Mike Chamberlain
9
Lub, jako kompilator, zawiedli. Jeśli konstruktor nie zainicjuje właściwości, ale robi to hak cyklu życia, a kompilator nie rozpoznaje tego.
Mukus
26
To nie jest odpowiedzialność kompilatora TS. W przeciwieństwie do niektórych innych języków (np. C #), JS (a zatem TS) nie wymaga inicjalizacji zmiennych przed użyciem. Lub, aby spojrzeć na to z innej strony, w JS wszystkie zmienne zadeklarowane za pomocą varlub letdomyślnie zainicjowane undefined. Ponadto właściwości instancji klasy można zadeklarować jako takie, więc class C { constructor() { this.myVar = undefined; } }jest to całkowicie legalne. Wreszcie haki cyklu życia zależą od struktury; na przykład Angular i React wdrażają je inaczej. Dlatego nie można oczekiwać, że kompilator TS będzie o nich myślał.
Mike Chamberlain,
1
Czy istnieje uzasadniony przypadek użycia dla operatora uderzenia, biorąc pod uwagę niesamowitość analizy typu opartej na przepływie kontroli w TS?
Eugene Karataev
1
@EugeneKarataev Tak, frameworki często inicjują zmienne wewnątrz siebie, a analiza przepływu kontroli ts nie może tego złapać. Jego użycie jest z pewnością ograniczone, ale natrafisz na przypadki, w których ich potrzebujesz.
arg20