Подтвердить что ты не робот

Как различные варианты перечислений работают в TypeScript?

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 компилятора - как это взаимодействует с ними?

4b9b3361

Ответ 1

Существует четыре разных аспекта перечисления в 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".

"declare" или "ambient"

В TypeScript вы можете "объявить" вещи, о которых должен знать компилятор, но на самом деле не испускать код. Это полезно, когда у вас есть библиотеки, такие как jQuery, которые определяют некоторый объект (например, $), о котором вы хотите вводить информацию о типе, но не нуждаетесь в каком-либо коде, создаваемом компилятором. Спецификация и другая документация относятся к объявлениям, сделанным таким образом, как находящимся в "окружающем" контексте; важно отметить, что все объявления в файле .d.ts являются "окружающими" (либо требуют явного модификатора declare, либо неявно, в зависимости от типа объявления).

"встраивание"

По соображениям производительности и размера кода часто предпочтительнее иметь ссылку на член перечисления, замененный его числовым эквивалентом при компиляции:

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

Спектр вызывает эту подстановку, я буду называть ее inlining, потому что она звучит круче. Иногда вы не хотите, чтобы члены перечисления были встроены, например, потому что значение перечисления может измениться в будущей версии API.


Перечисления, как они работают?

Позвольте разбить это на каждый аспект перечисления. К сожалению, каждый из этих четырех разделов будет ссылаться на термины от всех остальных, поэтому вам, вероятно, придется читать все это несколько раз.

вычисляемый vs не вычисленный (постоянный)

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

Вычисленный член перечисления - это элемент, значение которого неизвестно во время компиляции. Разумеется, ссылки на вычисленных членов не могут быть встроены. Напротив, некорректный член перечисления один раз, значение которого известно во время компиляции. Ссылки на невычислимые члены всегда вставляются.

Какие перечисляемые члены вычисляются и которые не вычисляются? Во-первых, все члены перечисления const являются постоянными (т.е. Не вычисляются), как следует из названия. Для не-const перечисления это зависит от того, смотрите ли вы на enum (declare) enum или non-ambient enum.

Член declare enum (т.е. окружение enum) является постоянным тогда и только тогда, когда он имеет инициализатор. В противном случае он вычисляется. Обратите внимание, что в declare enum допускаются только числовые инициализаторы. Пример:

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

Наконец, члены non-declare non-const перечислены всегда считаются вычисляемыми. Однако их инициализирующие выражения сводятся к константам, если они вычисляются во время компиляции. Это означает, что члены non-const enum никогда не привязаны (это поведение изменилось в TypeScript 1.5, см. "Изменения в TypeScript" внизу)

const vs non-const

Const

Объявление enum может иметь модификатор const. Если перечисление const, все ссылки на его члены встроены.

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

const перечисления не создают объект поиска при компиляции. По этой причине это ошибка для ссылки Foo в приведенном выше коде, кроме как часть ссылки на элемент. Объект Foo будет присутствовать во время выполнения.

неконстантная

Если в объявлении перечисления нет модификатора const, ссылки на его члены вставляются только в том случае, если член не вычисляется. Неконстантный, non-declare enum будет создавать объект поиска.

declare (ambient) vs non-declare

Важным предисловием является то, что 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 не отличается от a const enum, за исключением случая --preserveConstEnums (см. ниже).

не-DECLARE

Не объявляемое перечисление создает объект поиска, если он не является const. Инкрустация описана выше.

- preserveConstEnums flag

Этот флаг имеет ровно один эффект: un-declare const перечисляет объект поиска. Вложение не влияет. Это полезно для отладки.


Общие ошибки

Наиболее распространенной ошибкой является использование 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/issues/2183), чтобы все члены не- declare non-const перечисления обрабатываются как вычисляемые, даже если они явно инициализируются литералом. Это, так сказать, "расстегивает ребенка", делая поведение inlining более предсказуемым и более чистое разделение понятия const enum от регулярного enum. До этого изменения не вычисленные члены неконстантных перечислений были более агрессивно настроены.