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
编译器标志 - 这与这些标志如何相互作用?
2 回答
您需要注意TypeScript中的枚举有四个不同的方面 . 首先,一些定义:
“查找对象”
如果你写这个枚举:
TypeScript将发出以下对象:
我将其称为查找对象 . 它的目的是双重的:用作从字符串到数字的映射,例如写
Foo.X
或Foo['X']
时,用作从数字到字符串的映射 . 反向映射对于调试或记录目的很有用 - 您通常会使用值0
或1
并希望获得相应的字符串"X"
或"Y"
.“声明”或“环境”
在TypeScript中,您可以"declare"编译器应该知道的事情,但实际上不会为其发出代码 . 当你有像jQuery这样的库定义一些你想要类型信息但不需要编译器创建的代码的对象(例如
$
)时,这很有用 . 规范和其他文档指的是这样做的声明在"ambient"上下文中;重要的是要注意.d.ts
文件中的所有声明都是"ambient"(要么需要显式的declare
修饰符,要么隐式地使用它,具体取决于声明类型) .“内联”
出于性能和代码大小的原因,通常最好在编译时将枚举成员的引用替换为其数字等效项:
该规范称之为替换,我将其称为内联,因为它听起来更酷 . 有时您不希望枚举枚举成员,例如因为枚举值可能会在API的未来版本中发生变化 .
枚举,它们如何运作?
让我们通过枚举的每个方面来打破这个 . 不幸的是,这四个部分中的每一部分都将引用所有其他部分的术语,因此您可能需要不止一次地阅读这整个部分 .
计算与非计算(常数)
枚举成员可以是否可以计算 . 规范将非计算成员调用为常量,但我将其称为非计算,以避免与const混淆 .
计算的枚举成员是在编译时不知道其值的成员 . 当然,不能内联对计算成员的引用 . 相反,非计算枚举成员的值在编译时是已知的 . 始终内联对非计算成员的引用 .
哪些枚举成员是计算的,哪些是非计算的?首先,顾名思义,
const
枚举的所有成员都是常量(即非计算的) . 对于非常量枚举,它取决于您是在查看环境(声明)枚举还是非环境枚举 .declare enum
(即环境枚举)的成员当且仅当它具有初始化器时才是常量 . 否则,计算它 . 请注意,在declare enum
中,只允许使用数字初始值设定项 . 例:最后,始终认为非声明非const枚举的成员是计算的 . 但是,如果它们在编译时可计算,则它们的初始化表达式会减少到常量 . 这意味着非const枚举成员永远不会内联(在TypeScript 1.5中更改了此行为,请参阅底部的“TypeScript中的更改”)
const vs非const
const
枚举声明可以具有
const
修饰符 . 如果枚举是const
,则所有对其成员的引用都是内联的 .const枚举在编译时不会生成查找对象 . 因此,在上述代码中引用
Foo
是错误的,除非作为成员引用的一部分 . 运行时不会出现Foo
对象 .非const
如果枚举声明没有
const
修饰符,则仅当成员未计算时才会内联对其成员的引用 . 非const,非声明枚举将生成查找对象 .declare(ambient)vs non-declare
一个重要的前言是TypeScript中的
declare
具有非常特定的含义:该对象存在于其他地方 . 它用于描述现有对象 . 使用declare
定义对象不要't actually exist can have bad consequences; we'以后再探讨 .宣布
declare enum
不会发出查找对象 . 如果计算了这些成员,则会内联对其成员的引用(参见上面的计算与非计算) .重要的是要注意允许对
declare enum
的其他形式的引用,例如此代码不是编译错误,但会在运行时失败:此错误属于"Don't lie to the compiler"类别 . 如果在运行时没有名为
Foo
的对象,请不要写declare enum Foo
!declare const enum
与const enum
没有区别,除了--preserveConstEnums(见下文) .非申报
如果非声明枚举不是
const
,则它会生成查找对象 . 上面描述了内联 .--preserveConstEnums标志
此标志只有一个效果:非声明const枚举将发出查找对象 . 内联不受影响 . 这对调试很有用 .
常见错误
最常见的错误是当常规
enum
或const enum
更合适时使用declare enum
. 一个常见的形式是:记住黄金法则:从来没有
declare
实际上不存在的东西 . 如果您总是想要内联,请使用const enum
;如果需要查找对象,请使用enum
.TypeScript中的更改
在TypeScript 1.4和1.5之间,行为发生了变化(请参阅https://github.com/Microsoft/TypeScript/issues/2183),以使非声明非const枚举的所有成员都被视为已计算,即使它们已使用文字显式初始化 . 可以这么说,"unsplit the baby",使内联行为更可预测,更清晰地将
const enum
的概念与常规enum
分开 . 在此更改之前,非const enums的非计算成员被更加积极地内联 .这里有一些事情发生 . 让我们逐个进行 .
枚举
首先,一个普通的老枚举 . 编译为JavaScript时,这将发出一个查找表 .
查找表如下所示:
然后当你在TypeScript中有
Cheese.Brie
时,它会在JavaScript中发出Cheese.Brie
,其值为0.Cheese[0]
发出Cheese[0]
并实际计算为"Brie"
.const enum
实际上没有为此发出代码!它的值是内联的 . 以下内容在JavaScript中发出值0:
const enum
s'内联可能因性能原因而有用 .但是
Bread[0]
怎么样?这将在运行时出错,您的编译器应该捕获它 . 那里有's no lookup table and the compiler doesn'吨内联 .请注意,在上面的情况中, - prepareConstEnums标志将导致Bread发出查找表 . 它的 Value 仍然会被内联 .
声明枚举
与
declare
的其他用法一样,declare
不会发出代码,并且您希望在其他位置定义实际代码 . 这不会发出查找表:Wine.Red
在JavaScript中发出Wine.Red
,但除非您在别处定义了't be any Wine lookup table to reference so it',否则会出现错误 .declare const enum
这不会发出查找表:
但它确实内联!
Fruit.Apple
发出0.但是Fruit[0]
将在运行时错误输出,因为它没有查找表's not inlined and there' .我在this操场上写了这篇文章 . 我建议在那里玩,以了解哪个TypeScript发出哪个JavaScript .