Як працюють різні варіанти enum у TypeScript?


116

У TypeScript існує маса різних способів визначення перерахунку:

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

Якщо я спробую використати значення Gammaпід час виконання, я отримаю помилку, оскільки Gammaвона не визначена, але це не стосується Deltaабо Alpha? Що означає constабо declareозначає тут декларації?

Також є preserveConstEnumsпрапор компілятора - як це взаємодіє з ними?


1
Я щойно написав статтю про це , хоча це має більше спільного з порівнянням const з non const enums
joelmdev

Відповіді:


247

Існує чотири різні аспекти для перерахунків у TypeScript, про які потрібно знати. По-перше, деякі визначення:

"об'єкт пошуку"

Якщо ви пишете цей перелік:

enum Foo { X, Y }

TypeScript видасть такий об'єкт:

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

Я буду називати це об'єктом пошуку . Її призначення двояке: слугувати як відображення від рядків до чисел , наприклад, під час написання Foo.Xабо Foo['X'], і як зіставлення з чисел у рядки . Це зворотне відображення корисно для налагодження або ведення журналу - ви часто матимете значення 0або 1хочете отримати відповідний рядок "X"або "Y".

"оголосити" або " навколишнє середовище "

У TypeScript ви можете "оголосити" речі, про які повинен знати компілятор, але насправді не випромінювати код. Це корисно, коли у вас є бібліотеки типу jQuery, які визначають якийсь об'єкт (наприклад $), про який ви хочете ввести інформацію, але не потребує коду, створеного компілятором. Специфікація та інша документація стосується заяв, зроблених таким чином, як такі, що перебувають у "оточуючому" контексті; важливо зазначити, що всі декларації у .d.tsфайлі є "оточуючими" (або вимагають явного declareмодифікатора, або мають його неявно, залежно від типу декларації).

"вкладиш"

З міркувань продуктивності та розміру коду часто бажано, щоб при компіляції посилання на члена enum було замінено його числовим еквівалентом:

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

Специфікація називає цю заміну , я буду називати її вкладною, тому що вона звучить крутіше. Іноді ви не хочете, щоб члени enum були накреслені, наприклад, тому що значення enum може змінитися в майбутній версії API.


Енуми, як вони працюють?

Давайте розбимо це на кожен аспект перерахунку. На жаль, кожен з цих чотирьох розділів збирається посилатися на терміни з усіх інших, тому вам, ймовірно, потрібно буде прочитати всю цю справу не раз.

обчислені проти не обчислені (постійні)

Члени Enum можуть бути обчислені або ні. Специфікація називає незчислених членів постійними , але я буду називати їх невичисленими, щоб уникнути плутанини з const .

Обчислюються член перерахування один, значення якого не відомо під час компіляції. Посилання на обчислені члени, звичайно, не можуть бути накреслені. І навпаки, не обчислений член enum - це один раз, значення якого відомо під час компіляції. Посилання на не обчислених членів завжди накреслені.

Які члени перерахунку обчислюються, а які не обчислюються? По-перше, всі члени constenum є постійними (тобто не обчисленими), як випливає з назви. Для non-const enum це залежить від того, ви дивитесь на ambient (оголосити) enum або non-ambient enum.

Член declare enum(наприклад, навколишнє середовище) є постійним, якщо і тільки якщо він має ініціалізатор. В іншому випадку він обчислюється. Зауважте, що в а declare enumдозволяються лише числові ініціалізатори. Приклад:

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

Нарешті, члени незадекларованих неконституційних перерахунків завжди вважаються обчисленими. Однак їх ініціалізаційні вирази зводяться до констант, якщо вони обчислюються під час компіляції. Це означає, що учасники безперервного перерахунку ніколи не вказуються (така поведінка змінилася в TypeScript 1.5, див. "Зміни в TypeScript" внизу)

const vs non-const

const

Декларація перерахунку може мати constмодифікатор. Якщо перерахунок є const, усі посилання на його членів накреслені.

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

Перерахунки const не створюють об'єкт пошуку при компілюванні. З цієї причини помилка посилання Fooна вищезазначений код, за винятком як частини посилання члена. Жоден Fooоб’єкт не буде присутній під час виконання.

неконст

Якщо в декларації перерахунку немає constмодифікатора, посилання на його членів вкладаються лише в тому випадку, якщо член не обчислюється. Неперевірений, недекларований перерахунок створить об'єкт пошуку.

заявляти (оточувати) проти не декларувати

Важлива передмова полягає declareв тому, що в TypeScript є дуже специфічне значення: Цей об'єкт існує десь ще . Це для опису існуючих об'єктів. Використання declareдля визначення об'єктів, які насправді не існують, може мати погані наслідки; ми вивчимо їх пізніше.

заявити

A declare enumоб'єкт пошуку не випромінює. Посилання на його членів накреслені, якщо ці члени обчислюються (див. Вище про обчислені проти не обчислені).

Важливо відзначити , що інші форми посилання на declare enum будуть дозволені, наприклад , цей код НЕ помилка компіляції , але буде НЕ в змозі під час виконання:

// 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

Ця помилка підпадає під категорію "Не брешіть компілятору". Якщо у вас немає об’єкта, названого Fooпід час виконання, не пишіть declare enum Foo!

A declare const enumне відрізняється від а const enum, за винятком випадків --preserveConstEnums (див. Нижче).

не декларувати

Недекларований перерахунок створює об'єкт пошуку, якщо його немає const. Вкладиш описано вище.

- прапор conserveConstEnums

Цей прапор має точно один ефект: недекларувати const перерахунки випромінюють об'єкт пошуку. Вкладиш не впливає. Це корисно для налагодження.


Поширені помилки

Найпоширенішою помилкою є використання a, declare enumколи регулярне enumабо const enumбуло б більш доречним. Поширена форма така:

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]);
}

Пам’ятайте золоте правило: ніколи declareне існують речі, які насправді не існують . Використовуйте, const enumякщо ви хочете завжди вбудовувати або enumякщо ви хочете об'єкт пошуку.


Зміни в TypeScript

Між TypeScript 1.4 і 1.5 відбулася зміна поведінки (див. Https://github.com/Microsoft/TypeScript/isissue/2183 ), щоб змусити всіх членів незадекларованих перерахунків, які не вважають const, трактуватись як обчислені, навіть якщо вони явно ініціалізовані з літералом. Це, так би мовити, немовля, що робить розкладну поведінку більш передбачуваним і більш чітко відокремлює концепцію const enumвід звичайної enum. До цієї зміни некомп'ютовані члени неперевірених перерахунків були підкреслені більш агресивно.


6
Справді приголомшлива відповідь. Це прояснило стільки речей для мене, а не лише перераховує.
Кларк

1
Я хотів би, щоб я міг проголосувати за вас не раз ... не знав про цю суттєву зміну. При правильній семантичній версії це можна вважати ударом до основної версії: - /
mfeineis

Дуже корисне порівняння різних enumтипів, дякую!
Маріус Шульц

@Ryan це дуже корисно, дякую! Тепер нам просто потрібні Web Essentials 2015 для створення відповідних constдля оголошених типів перерахунків.
styfle

19
Ця відповідь, здається, вникає в деталі, пояснюючи ситуацію в 1.4, а потім в самому кінці вона говорить: "але 1.5 все це змінило, і тепер це набагато простіше". Якщо припустити, що я все правильно розумію, ця організація стає все більш невідповідною, оскільки ця відповідь старіє: я настійно рекомендую спочатку поставити простішу, поточну ситуацію , і лише після цього сказати: "але якщо ви використовуєте 1.4 або раніше, речі трохи складніше. "
KRyan

33

Тут відбувається кілька речей. Пройдемо по кожному випадку.

перерахувати

enum Cheese { Brie, Cheddar }

По-перше, звичайний старий перелік. Після компіляції в JavaScript це відображатиме таблицю пошуку.

Таблиця пошуку виглядає так:

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

Потім, коли у вас є Cheese.BrieTypeScript, він видає Cheese.BrieJavaScript, який оцінює до 0. Cheese[0]випускає Cheese[0]і фактично оцінює до "Brie".

const enum

const enum Bread { Rye, Wheat }

Жодного коду насправді для цього не випромінюється! Його значення підкреслені. Нижче наведено значення 0 у JavaScript:

Bread.Rye
Bread['Rye']

const enums 'вкладка може бути корисною з міркувань продуктивності.

А як же Bread[0]? Це призведе до помилки під час виконання, і ваш компілятор повинен це зробити. Тут немає таблиці пошуку, і компілятор не вказує тут.

Зауважте, що у наведеному вище випадку прапор --preserveConstEnums призведе до того, що "Хліб" випромінює таблицю пошуку. Його значення все ж будуть підкреслені.

оголосити перерахунок

Як і в інших випадках використання declare, він declareне видає код і очікує, що ви визначили фактичний код в іншому місці. Це не видає таблицю пошуку:

declare enum Wine { Red, Wine }

Wine.Redвипускає Wine.Redв JavaScript, але не буде жодної таблиці пошуку Wine для посилання, тому це помилка, якщо ви її не визначили в іншому місці.

оголосити const enum

Це не видає таблицю пошуку:

declare const enum Fruit { Apple, Pear }

Але це робиться в Інтернеті! Fruit.Appleвипускає 0. Але знову Fruit[0]буде помилка під час виконання, оскільки вона не вбудована і немає таблиці пошуку.

Я написав це на цьому майданчику. Я рекомендую пограти там, щоб зрозуміти, який TypeScript випромінює JavaScript.


1
Я рекомендую оновити цю відповідь: Станом на Typescript 3.3.3, Bread[0]видається помилка компілятора: "Доступ до члена const enum можна отримати лише за допомогою рядкового літералу."
chharvey

1
Гм ... чи відрізняється від того, що говорить відповідь? "Але що з Bread [0]? Це буде помилкою під час виконання, і ваш компілятор повинен його наздогнати. Таблиці пошуку немає, і компілятор не вказує тут."
Кет
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.