Вы должны знать четыре различных аспекта перечислений в 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 Foo { X = 4 }
var y = Foo.X; // emits "var y = 4";
В спецификации эта подстановка называется , я назову ее встраиванием, потому что она звучит круче. Иногда вам не нужно, чтобы члены перечисления были встроены, например, потому что значение перечисления может измениться в будущей версии API.
Enums, как они работают?
Давайте разберем это по каждому аспекту перечисления. К сожалению, каждый из этих четырех разделов будет ссылаться на термины из всех остальных, поэтому вам, вероятно, придется прочитать все это целиком более одного раза.
вычисленное vs невычисленное (константа)
Члены перечисления могут быть вычислены или нет. Спецификация вызовов , не вычисляемые члены постоянной , но я буду называть их не вычисленный , чтобы избежать путаницы с сопзЬ .
Вычисляются член перечисления один, значение которого не известно во время компиляции. Ссылки на вычисляемые члены, конечно, не могут быть встроены. И наоборот, не-вычисленный членом является перечислением раз, значение которого будет известно во время компиляции. Ссылки на невычисляемые элементы всегда встраиваются.
Какие члены перечисления вычисляются, а какие не вычисляются? Во-первых, все члены const
перечисления постоянны (т. Е. Не вычисляются), как следует из названия. Для неконстантного перечисления это зависит от того, смотрите ли вы на внешнее (объявленное) перечисление или на внешнее перечисление.
Член declare enum
(то есть окружающее перечисление) является константой тогда и только тогда, когда у него есть инициализатор. В противном случае он вычисляется. Обратите внимание, что в a 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
Объявление перечисления может иметь const
модификатор. Если перечисление есть const
, все ссылки на его члены встроены.
const enum Foo { A = 4 }
var x = Foo.A; // emitted as "var x = 4;", always
Перечисления const не создают объект поиска при компиляции. По этой причине ссылка Foo
в приведенном выше коде является ошибкой, за исключением ссылки на член. Foo
Во время выполнения не будет никаких объектов.
неконстантный
Если объявление перечисления не имеет const
модификатора, ссылки на его элементы встроены, только если член не вычисляется. Неконстантное перечисление без объявления создаст объект поиска.
объявить (окружающий) vs не объявить
Важное предисловие состоит в том, что 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 (см. Ниже).
не объявлять
Перечисление без объявления создает объект поиска, если это не так const
. Встраивание описано выше.
--preserveConstEnums флаг
Этот флаг имеет только один эффект: перечисления констант без объявления будут генерировать объект поиска. Встраивание не затрагивается. Это полезно для отладки.
Общие ошибки
Самая распространенная ошибка - использовать 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 ), чтобы все члены не объявленных неконстантных перечислений считались вычисленными, даже если они явно инициализируются литералом. Это, так сказать, «нерасщепляет ребенка», делая встраиваемое поведение более предсказуемым и более четко отделяя концепцию const enum
от обычного enum
. До этого изменения невычисляемые члены неконстантных перечислений встраивались более агрессивно.