在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没有定义,但DeltaAlpha不是这种情况? 这里的const或者declare含义是什么?

还有一个preserveConstEnums编译器标志 – 这是如何与这些交互?

在TypeScript中有四个不同的方面需要注意。 首先是一些定义:

“查找对象”

如果你写这个枚举:

 enum Foo { X, Y } 

TypeScript将会发出以下对象:

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

我将把它称为查找对象 。 它的目的有两个:作为从string数字的映射,例如在写Foo.XFoo['X'] ,作为从数字string的映射。 该反向映射对于debugging或日志logging的目的非常有用 – 您通常会得到值01并希望获得相应的string"X""Y"

“声明”或“ 环境

在TypeScript中,你可以“声明”编译器应该知道的东西,但是并不真正发出代码。 当你有像jQuery这样的库来定义你想要的types信息,但是不需要编译器创build的任何代码的对象(例如$ )的时候,这是非常有用的。 规范和其他文档是指以这种方式进行的声明处于“环境”环境中; 注意.d.ts文件中的所有声明都是“环境”(根据声明types,要么需要显式declare修饰符,要么隐含declare修饰符)是很重要的。

“内联”

出于性能和代码大小的原因,通常最好在编译时引用一个枚举成员,用它的数字等效replace:

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

规范称这种替代 ,我会称它内联,因为它听起来更酷。 有时你希望enum成员被内联,例如,因为enum的值可能会在未来版本的API中改变。


枚举,它们是如何工作的?

让我们来分析枚举的每个方面。 不幸的是,这四个部分中的每一部分都将引用所有其他部分的术语,因此您可能需要不止一次地阅读这些内容。

计算与非计算(常量)

枚举成员既可以计算也可以不计算 。 该规范调用非计算成员常量 ,但我会叫他们不计算,以避免与const混淆。

一个计算枚举成员是一个在编译时不知道它的值的成员。 计算成员的引用当然不能被内联。 相反,一个非计算的枚举成员是在编译时已知的值。 对非计算成员的引用总是内联的。

哪些枚举成员被计算,哪些是不被计算的? 首先,一个const所有成员都是不变的(即不计算),顾名思义。 对于非常量枚举,这取决于您是否在查看环境 (声明)枚举或非环境枚举。

declare enum成员(即环境枚举)是恒定的, 当且仅当它有一个初始化器。 否则,它被计算。 请注意,在一个declare enum ,只允许数字初始值设定项。 例:

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

最后,非声明非常量枚举的成员总是被认为是计算的。 但是,如果它们在编译时是可计算的,则它们的初始化expression式会降低到常量。 这意味着非常量枚举成员不会内联(在TypeScript 1.5中更改此行为,请参阅底部的“TypeScript中的更改”)

常量与非常量

常量

一个枚举声明可以有const修饰符。 如果一个枚举是const ,那么所有对其成员的引用都是内联的。

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

编译时,常量枚举不会生成查找对象。 出于这个原因,在上面的代码中引用Foo是一个错误,除了作为成员引用的一部分。 在运行时不会有Foo对象。

非const

如果一个枚举声明没有const修饰符,那么只有当这个成员是非计算的时候,对它的成员的引用才会被内联。 一个非const的非声明枚举将产生一个查找对象。

申报(环境)与非申报

一个重要的前言是,在TypeScript中的declare有一个非常具体的含义: 这个对象存在于别的地方 。 这是为了描述现有的对象。 使用declare来定义实际上不存在的对象可能会造成不好的后果。 我们稍后再探讨。

宣布

declare enum不会发出查找对象。 如果计算这些成员,则对其成员的引用进行内联(参见上面有关计算与非计算)。

请注意,其他forms的引用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

除了–preserveConstEnums(参见下文)外, declare const enum不相同。

非申报

如果非声明枚举不是const则它将生成一个查找对象。 上面描述了内联。

–preserveConstEnums标志

这个标志只有一个作用:非声明常量枚举将发出一个查找对象。 内联不受影响。 这对debugging很有用。


常见错误

最常见的错误是当一个常规enumconst enum更合适时使用一个declare enum 。 一个常见的forms是这样的:

 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如果您想要查找对象,则使用const enum


TypeScript中的更改

在TypeScript 1.4和1.5之间,行为发生了变化(请参阅https://github.com/Microsoft/TypeScript/issues/2183 ),以便将非声明非常量枚举的所有成员视为已计算,即使他们显式初始化一个文字。 可以说,这个“不分裂婴儿”,使得内联行为更可预测,更清楚地将const enum概念与常规enum分开。 在这个变化之前,非计算的非常量枚举的成员被更为积极地内联。