在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
编译器标志 – 这是如何与这些交互?
在TypeScript中有四个不同的方面需要注意。 首先是一些定义:
“查找对象”
如果你写这个枚举:
enum Foo { X, Y }
TypeScript将会发出以下对象:
var Foo; (function (Foo) { Foo[Foo["X"] = 0] = "X"; Foo[Foo["Y"] = 1] = "Y"; })(Foo || (Foo = {}));
我将把它称为查找对象 。 它的目的有两个:作为从string到数字的映射,例如在写Foo.X
或Foo['X']
,作为从数字到string的映射。 该反向映射对于debugging或日志logging的目的非常有用 – 您通常会得到值0
或1
并希望获得相应的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很有用。
常见错误
最常见的错误是当一个常规enum
或const 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
分开。 在这个变化之前,非计算的非常量枚举的成员被更为积极地内联。