Istnieją cztery różne aspekty wyliczeń w języku TypeScript, o których należy pamiętać. Najpierw kilka definicji:
„obiekt wyszukiwania”
Jeśli napiszesz to wyliczenie:
enum Foo { X, Y }
TypeScript wyemituje następujący obiekt:
var Foo;
(function (Foo) {
Foo[Foo["X"] = 0] = "X";
Foo[Foo["Y"] = 1] = "Y";
})(Foo || (Foo = {}));
Nazywam to obiektem wyszukiwania . Jego cel jest dwojaki: służy jako odwzorowanie ciągów znaków na liczby , np. Podczas pisania Foo.X
lub Foo['X']
, oraz służy jako odwzorowanie liczb na łańcuchy . To odwrotne odwzorowanie jest przydatne do debugowania lub rejestrowania - często będziesz mieć wartość 0
lub 1
i chcesz uzyskać odpowiedni ciąg "X"
lub "Y"
.
„deklaracja” lub „ otoczenie ”
W języku TypeScript można „zadeklarować” rzeczy, o których kompilator powinien wiedzieć, ale w rzeczywistości nie emitować kodu. Jest to przydatne, gdy masz biblioteki, takie jak jQuery, które definiują obiekt (np. $
), O którym chcesz wpisać informacje, ale nie potrzebujesz żadnego kodu utworzonego przez kompilator. Specyfikacja i inna dokumentacja odnosi się do deklaracji sporządzonych w ten sposób jako znajdujących się w kontekście „otoczenia”; należy zauważyć, że wszystkie deklaracje w .d.ts
pliku są „otoczone” (albo wymagają jawnego declare
modyfikatora, albo mają go niejawnie, w zależności od typu deklaracji).
„inlining”
Ze względu na wydajność i rozmiar kodu często preferowane jest zastąpienie odniesienia do elementu członkowskiego wyliczenia jego liczbowym odpowiednikiem podczas kompilacji:
enum Foo { X = 4 }
var y = Foo.X; // emits "var y = 4";
Specyfikacja nazywa to zastąpienie , nazwę to inlining, ponieważ brzmi fajniej. Czasami nie chcesz, aby elementy członkowskie wyliczenia były wstawiane, na przykład ponieważ wartość wyliczenia może ulec zmianie w przyszłej wersji interfejsu API.
Enum, jak to działa?
Podzielmy to na każdy aspekt wyliczenia. Niestety, każda z tych czterech sekcji będzie odnosić się do terminów ze wszystkich pozostałych, więc prawdopodobnie będziesz musiał przeczytać tę całość więcej niż raz.
obliczone a nieobliczone (stała)
Składowe wyliczenia mogą być obliczane lub nie. Specyfikacja wywołuje nieobliczone elementy składowe stałymi , ale będę je nazywać nieobliczonymi, aby uniknąć pomyłki z const .
Obliczane członkiem enum jest ten, którego wartość nie jest znany w czasie kompilacji. Oczywiście nie można wstawiać odwołań do obliczonych członków. Odwrotnie, niż wyliczona element wyliczenia jest po których wartość jest znana w czasie kompilacji. Odwołania do nieobliczonych elementów członkowskich są zawsze wstawiane.
Które elementy członkowskie wyliczenia są obliczane, a które nie? Po pierwsze, wszystkie elementy const
wyliczenia są stałe (tj. Nieobliczane), jak sama nazwa wskazuje. W przypadku wyliczenia innego niż stała, zależy to od tego, czy patrzysz na wyliczenie otoczenia (deklarowanie), czy wyliczenie inne niż otoczenia.
Element członkowski declare enum
(tj. Wyliczenie otoczenia) jest stały wtedy i tylko wtedy, gdy ma inicjator. W przeciwnym razie jest obliczany. Zauważ, że w a declare enum
dozwolone są tylko inicjatory numeryczne. Przykład:
declare enum Foo {
X, // Computed
Y = 2, // Non-computed
Z, // Computed! Not 3! Careful!
Q = 1 + 1 // Error
}
Na koniec, elementy składowe niezadeklarowanych wyliczeń innych niż stałe są zawsze traktowane jako obliczone. Jednak ich wyrażenia inicjujące są zredukowane do stałych, jeśli są obliczalne w czasie kompilacji. Oznacza to, że nie będące stałymi składowymi wyliczenia nigdy nie są wstawiane (to zachowanie zostało zmienione w języku TypeScript 1.5, zobacz „Zmiany w języku TypeScript” u dołu)
const vs non-const
konst
Deklaracja wyliczenia może mieć const
modyfikator. Jeśli wyliczenie to const
, wszystkie odwołania do jego członków są wstawione.
const enum Foo { A = 4 }
var x = Foo.A; // emitted as "var x = 4;", always
Wyliczenia const nie generują obiektu wyszukiwania podczas kompilacji. Z tego powodu błędem jest odwoływanie się Foo
do powyższego kodu, z wyjątkiem odniesienia do elementu członkowskiego. Żaden Foo
obiekt nie będzie obecny w czasie wykonywania.
non-const
Jeśli deklaracja wyliczenia nie ma const
modyfikatora, odwołania do jej elementów członkowskich są wstawiane tylko wtedy, gdy element członkowski nie jest obliczany. Wyliczenie inne niż stała, niezadeklarowane spowoduje utworzenie obiektu wyszukiwania.
deklarować (otoczenia) vs deklarować
Ważnym wstępem jest to, że declare
w TypeScript ma bardzo specyficzne znaczenie: ten obiekt istnieje gdzie indziej . Służy do opisywania istniejących obiektów. Używanie declare
do definiowania obiektów, które w rzeczywistości nie istnieją, może mieć złe konsekwencje; zbadamy je później.
ogłosić
A declare enum
nie wyemituje obiektu wyszukiwania. Odwołania do jego elementów członkowskich są wstawiane, jeśli te elementy członkowskie są obliczane (patrz powyżej na temat obliczonych i nieobliczonych).
Należy zauważyć, że dozwolone declare enum
są inne formy odwołań do a , np. Ten kod nie jest błędem kompilacji, ale zakończy się niepowodzeniem w czasie wykonywania:
// Note: Assume no other file has actually created a Foo var at runtime
declare enum Foo { Bar }
var s = 'Bar';
var b = Foo[s]; // Fails
Ten błąd należy do kategorii „Nie kłam kompilatorowi”. Jeśli nie masz obiektu nazwanego Foo
w czasie wykonywania, nie pisz declare enum Foo
!
A declare const enum
nie różni się od a const enum
, z wyjątkiem przypadku --preserveConstEnums (patrz poniżej).
nie deklarować
Niezadeklarowane wyliczenie tworzy obiekt wyszukiwania, jeśli nie jest const
. Inlining opisano powyżej.
--preserveConstEnums flaga
Ta flaga ma dokładnie jeden efekt: niezadeklarowane wyliczenia const będą emitować obiekt wyszukiwania. Nie ma to wpływu na podszewkę. Jest to przydatne do debugowania.
Powszechne błędy
Najczęstszym błędem jest użycie a, declare enum
gdy zwykły enum
lub const enum
byłby bardziej odpowiedni. Typowa forma jest taka:
module MyModule {
// Claiming this enum exists with 'declare', but it doesn't...
export declare enum Lies {
Foo = 0,
Bar = 1
}
var x = Lies.Foo; // Depend on inlining
}
module SomeOtherCode {
// x ends up as 'undefined' at runtime
import x = MyModule.Lies;
// Try to use lookup object, which ought to exist
// runtime error, canot read property 0 of undefined
console.log(x[x.Foo]);
}
Pamiętaj o złotej zasadzie: nigdy declare
rzeczy, które nie istnieją . Użyj, const enum
jeśli chcesz zawsze wstawiać lub enum
jeśli chcesz, aby obiekt wyszukiwania.
Zmiany w TypeScript
Pomiędzy TypeScript 1.4 i 1.5 nastąpiła zmiana w zachowaniu (patrz https://github.com/Microsoft/TypeScript/issues/2183 ), aby wszystkie składowe niezadeklarowanych wyliczeń innych niż const były traktowane jako obliczone, nawet jeśli są jawnie zainicjowane literałem. To, że tak powiem, „rozłupać dziecko”, czyniąc zachowanie inlining bardziej przewidywalnym i wyraźniej oddzielając koncepcję const enum
od zwykłej enum
. Przed tą zmianą nieobliczane elementy składowe wyliczeń innych niż const były wstawiane bardziej agresywnie.