Jak działają różne warianty enum w maszynopisie?

TypeScript ma kilka różnych sposobów definiowania enum:

enum Alpha { X, Y, Z }
const enum Beta { X, Y, Z }
declare enum Gamma { X, Y, Z }
declare const enum Delta { X, Y, Z }

Jeśli próbuję użyć wartości z Gamma w czasie wykonywania, pojawia się błąd, ponieważ Gamma nie jest zdefiniowana, ale nie dotyczy to Delta lub Alpha? Co oznacza const lub declare na deklaracjach tutaj?

Jest też preserveConstEnums flaga kompilatora - jak to oddziałuje z nimi?

Author: Ryan Cavanaugh, 2015-03-02

2 answers

Istnieją cztery różne aspekty enums w maszynopisie, których musisz być świadomy. Po pierwsze, niektóre definicje:

"lookup object"

Jeśli napiszesz to enum:

enum Foo { X, Y }

TypeScript będzie emitował następujący obiekt:

var Foo;
(function (Foo) {
    Foo[Foo["X"] = 0] = "X";
    Foo[Foo["Y"] = 1] = "Y";
})(Foo || (Foo = {}));

Odwołam się do tego jako obiekt lookup . Jego cel jest dwojaki: służyć jako odwzorowanie z ciągów do liczb , np. przy zapisie Foo.X lub Foo['X'], oraz służyć jako odwzorowanie z liczby do ciągi . To odwrotne mapowanie jest przydatne do debugowania lub rejestrowania - często będziesz mieć wartość 0 lub 1 i chcesz uzyskać odpowiedni ciąg "X" lub "Y".

"declare "or " ambient "

W maszynopisie można "deklarować" rzeczy, o których kompilator powinien wiedzieć, ale nie emitować kodu. Jest to przydatne, gdy masz biblioteki takie jak jQuery, które definiują jakiś obiekt (np. $), który chcesz uzyskać informacje o typie, ale nie potrzebujesz żadnego kodu stworzonego przez kompilator. Spec i inna dokumentacja odnoszą się do deklaracji złożonych w ten sposób jako znajdujących się w kontekście "ambient"; ważne jest, aby pamiętać, że wszystkie deklaracje w pliku .d.ts są "ambient" (wymagają jawnego modyfikatora declare lub mają je w sposób niejawny, w zależności od typu deklaracji).

"inlining"

Ze względu na wydajność i rozmiar kodu, często lepiej jest mieć odniesienie do element enum zastąpiony przez jego odpowiednik liczbowy podczas kompilacji:

enum Foo { X = 4 }
var y = Foo.X; // emits "var y = 4";

Spec nazywa to substytucja , ja nazwę To inlining ponieważ brzmi chłodniej. Czasami nie chcesz, aby członkowie enum byli inlinowani, na przykład dlatego, że wartość enum może ulec zmianie w przyszłej wersji API.


Enums, jak one działają?

Podzielmy to według każdego aspektu enum. Niestety, każda z tych czterech sekcji jest przechodząc do terminów referencyjnych od wszystkich innych, więc prawdopodobnie będziesz musiał przeczytać to wszystko więcej niż raz.

Obliczona vs Nie obliczona (stała)

Członkowie Enum mogą być albo obliczane , albo nie. Spec nazywa nieprzeliczonymi członkami stałymi , ale zadzwonię do nich nieprzeliczonymi , aby uniknąć pomyłki z const .

Aobliczony członek enum to taki, którego wartość nie jest znana w czasie kompilacji. Wpisy w tematyce obliczony oczywiście członkowie nie mogą być łączeni. Natomiast nieprzeliczalny członek enum jest raz, którego wartość jest znana w czasie kompilacji. Odniesienia do niewyliczonych członków są zawsze inline.

Które elementy enum są obliczane, a które nie? Po pierwsze, wszystkie elementy const enum są stałe (tzn. nie są obliczane), jak sama nazwa wskazuje. W przypadku nie-const enum, zależy to od tego, czy patrzysz na ambient (declare) enum, czy na nie-ambient enum.

Członek declare enum (tj. enum otoczenia) jest stały wtedy i tylko wtedy, gdy mA inicjalizator. W przeciwnym razie jest obliczana. Zauważ, że w declare enum dozwolone są tylko numeryczne inicjalizatory. Przykład:

declare enum Foo {
    X, // Computed
    Y = 2, // Non-computed
    Z, // Computed! Not 3! Careful!
    Q = 1 + 1 // Error
}

Wreszcie, członkowie nie zadeklarowanych nie const enums są zawsze uważane za obliczone. Jednak ich wyrażenia inicjalizujące są zredukowane do stałych, jeśli można je obliczyć w czasie kompilacji. Oznacza to, że nie-const enum członkowie nigdy nie są inlined (to zachowanie zmienione w maszynopisie 1.5, Zobacz "zmiany w maszynopisie" na dole)

Const vs non-const

Const

Deklaracja enum może mieć modyfikator const. Jeśli enum jest const, wszystkie odniesienia do jego członków w inline.

const enum Foo { A = 4 }
var x = Foo.A; // emitted as "var x = 4;", always

Const enums nie generuje obiektu lookup podczas kompilacji. Z tego powodu błędem jest odwoływanie się Foo w powyższym kodzie, chyba że jest to część odniesienia członka. Nie Foo obiekt będzie obecny na runtime.

Non-const

Jeśli deklaracja enum nie ma modyfikatora const, odniesienia do jej członków są inlinowane tylko wtedy, gdy element nie jest obliczany. Nie-const, nie-declare enum wytworzy obiekt lookup.

Declare (ambient) vs non-declare

Ważnym wstępem jest to, że declare w maszynopisie ma bardzo specyficzne znaczenie: ten obiekt istnieje gdzieś indziej . Służy do opisywania istniejących obiektów. Użycie declare do zdefiniowania przedmioty, które w rzeczywistości nie istnieją, mogą mieć złe konsekwencje; zbadamy je później.

Declare

A declare enum nie będzie emitować obiektu lookup. Odniesienia do jego członków są inlined, jeśli członkowie te są obliczone (patrz powyżej na obliczone vs Nie-obliczone).

Ważne jest, aby pamiętać, że inne formy odniesienia do declare enum jest to błąd kompilacji, ale nie jest to błąd kompilacji, który powoduje błąd kompilacji 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 spada w kategorii "nie kłam kompilatorowi". Jeśli nie masz obiektu o nazwie 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 niżej).

Non-declare

Nie deklarowany enum tworzy obiekt lookup, jeśli nie jest const. Inlining jest opisany powyżej.

--preserveConstEnums flaga

Ta flaga ma dokładnie jeden efekt: nie-zadeklarowane Enemy const emitują odnośnik obiekt. Inlining nie ma wpływu. Jest to przydatne do debugowania.


Typowe Błędy

Najczęstszym błędem jest użycie declare enum, gdy bardziej odpowiednie byłoby zwykłe enum lub const enum. Typowa forma to:

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 tak naprawdę nie istnieją . Użyj const enum, jeśli zawsze chcesz inlining, lub enum, jeśli chcesz obiekt lookup.


Zmiany w maszynopisie

Między Maszynopisem 1.4 i 1.5, nastąpiła zmiana w zachowaniu (zobacz https://github.com/Microsoft/TypeScript/issues/2183), aby wszystkie elementy nie-deklarujące nie-const enum były traktowane jako obliczone, nawet jeśli są jawnie inicjalizowane literalnie. To" odepnij dziecko", że tak powiem, czyniąc zachowanie inlining bardziej przewidywalnym i bardziej czystym oddzielającym pojęcie {31]} od zwykłego {34]}. Przed tą zmianą nieliczeni członkowie nieliczonych enumów byli wliczeni więcej agresywnie.

 144
Author: Ryan Cavanaugh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-28 16:10:19

Dzieje się tu kilka rzeczy. Sprawdźmy każdy przypadek.

Enum

enum Cheese { Brie, Cheddar }

Po pierwsze, zwykły stary enum. Po skompilowaniu do JavaScript, spowoduje to wyświetlenie tabeli wyszukiwania.

Tabela wyszukiwania wygląda tak:

var Cheese;
(function (Cheese) {
    Cheese[Cheese["Brie"] = 0] = "Brie";
    Cheese[Cheese["Cheddar"] = 1] = "Cheddar";
})(Cheese || (Cheese = {}));

Następnie, gdy masz Cheese.Brie w maszynopisie, emituje Cheese.Brie w JavaScript, który ocenia się na 0. Cheese[0] emituje Cheese[0] i faktycznie ocenia do "Brie".

Const enum

const enum Bread { Rye, Wheat }

Żaden kod nie jest faktycznie emitowany do tego! Jego wartości są inlined. W języku JavaScript wartość 0 emitowana jest w następujący sposób:

Bread.Rye
Bread['Rye']

const enumS ' inlining może być przydatny ze względu na wydajność.

Ale co z Bread[0]? Spowoduje to błąd w czasie wykonywania i kompilator powinien go złapać. Nie ma tabeli wyszukiwania, a kompilator nie jest tutaj wbudowany.

Zauważ, że w powyższym przypadku flaga --preserveConstEnums spowoduje wyświetlenie tabeli wyszukiwania. Jego wartości nadal będą jednak inlined.

Declare enum

Podobnie jak w przypadku innych zastosowań declare, declare nie emituje żadnego kodu i oczekuje, że kod został zdefiniowany gdzie indziej. To nie wyświetla tabeli lookup:

declare enum Wine { Red, Wine }

Wine.Red emituje Wine.Red W JavaScript, ale nie będzie żadnej tabeli wyszukiwania wine do odwołania, więc jest to błąd, chyba że zdefiniowałeś go gdzie indziej.

Declare const enum

To nie wyświetla tabeli wyszukiwania:

declare const enum Fruit { Apple, Pear }

Ale robi to w linii! Fruit.Apple emituje 0. Ale znowu {[18] } będzie błąd podczas wykonywania ponieważ nie jest inlined i nie ma tabeli wyszukiwania.

Napisałem to w Ten plac zabaw. Polecam grać tam, aby zrozumieć, który TypeScript emituje który JavaScript.

 3
Author: Kat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-14 02:58:17