[英]How do the different enum variants work in TypeScript?
TypeScript has a bunch of different ways to define an enum: 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 }
If I try to use a value from Gamma
at runtime, I get an error because Gamma
is not defined, but that's not the case for Delta
or Alpha
? 如果我在运行时尝试使用
Gamma
中的值,我会收到一个错误,因为未定义Gamma
,但Delta
或Alpha
不是这种情况? What does const
or declare
mean on the declarations here? const
或declare
对declare
含义是什么?
There's also a preserveConstEnums
compiler flag -- how does this interact with these? 还有一个
preserveConstEnums
编译器标志 - 这与这些标志如何相互作用?
There are four different aspects to enums in TypeScript you need to be aware of. 您需要注意TypeScript中的枚举有四个不同的方面。 First, some definitions:
首先,一些定义:
If you write this enum: 如果你写这个枚举:
enum Foo { X, Y }
TypeScript will emit the following object: TypeScript将发出以下对象:
var Foo;
(function (Foo) {
Foo[Foo["X"] = 0] = "X";
Foo[Foo["Y"] = 1] = "Y";
})(Foo || (Foo = {}));
I'll refer to this as the lookup object . 我将其称为查找对象 。 Its purpose is twofold: to serve as a mapping from strings to numbers , eg when writing
Foo.X
or Foo['X']
, and to serve as a mapping from numbers to strings . 它的目的是双重的:用作从字符串到数字的映射,例如在写入
Foo.X
或Foo['X']
,用作从数字到字符串的映射。 That reverse mapping is useful for debugging or logging purposes -- you will often have the value 0
or 1
and want to get the corresponding string "X"
or "Y"
. 反向映射对于调试或记录目的很有用 - 您通常会得到值
0
或1
并希望获得相应的字符串"X"
或"Y"
。
In TypeScript, you can "declare" things that the compiler should know about, but not actually emit code for. 在TypeScript中,您可以“声明”编译器应该知道的内容,但实际上不会为其发出代码。 This is useful when you have libraries like jQuery that define some object (eg
$
) that you want type information about, but don't need any code created by the compiler. 当你有像jQuery这样的库定义一些你想要类型信息但不需要编译器创建的代码的对象(例如
$
)时,这很有用。 The spec and other documentation refers to declarations made this way as being in an "ambient" context; 规范和其他文档将这种声明称为处于“环境”环境中; it is important to note that all declarations in a
.d.ts
file are "ambient" (either requiring an explicit declare
modifier or having it implicitly, depending on the declaration type). 重要的是要注意
.d.ts
文件中的所有声明都是“环境”(需要显式declare
修饰符或隐式declare
它,具体取决于声明类型)。
For performance and code size reasons, it's often preferable to have a reference to an enum member replaced by its numeric equivalent when compiled: 出于性能和代码大小的原因,通常最好在编译时将枚举成员的引用替换为其数字等效项:
enum Foo { X = 4 }
var y = Foo.X; // emits "var y = 4";
The spec calls this substitution , I will call it inlining because it sounds cooler. 该规范称之为替换 ,我将其称为内联,因为它听起来更酷。 Sometimes you will not want enum members to be inlined, for example because the enum value might change in a future version of the API.
有时您不希望枚举枚举成员,例如因为枚举值可能会在API的未来版本中发生变化。
Let's break this down by each aspect of an enum. 让我们通过枚举的每个方面来打破这个。 Unfortunately, each of these four sections is going to reference terms from all of the others, so you'll probably need to read this whole thing more than once.
不幸的是,这四个部分中的每一部分都将引用所有其他部分的术语,因此您可能需要不止一次地阅读这整个部分。
Enum members can either be computed or not. 枚举成员可以是否可以计算 。 The spec calls non-computed members constant , but I'll call them non-computed to avoid confusion with const .
规范将非计算成员调用为常量 ,但我将其称为非计算,以避免与const混淆。
A computed enum member is one whose value is not known at compile-time. 计算的枚举成员是在编译时不知道其值的成员。 References to computed members cannot be inlined, of course.
当然,不能内联对计算成员的引用。 Conversely, a non-computed enum member is once whose value is known at compile-time.
相反, 非计算枚举成员的值在编译时是已知的。 References to non-computed members are always inlined.
始终内联对非计算成员的引用。
Which enum members are computed and which are non-computed? 哪些枚举成员是计算的,哪些是非计算的? First, all members of a
const
enum are constant (ie non-computed), as the name implies. 首先,
const
enum的所有成员都是常量(即非计算的),顾名思义。 For a non-const enum, it depends on whether you're looking at an ambient (declare) enum or a non-ambient enum. 对于非常量枚举,它取决于您是在查看环境 (声明)枚举还是非环境枚举。
A member of a declare enum
(ie ambient enum) is constant if and only if it has an initializer. declare enum
(即环境枚举)的成员当且仅当它具有初始化器时才是常量。 Otherwise, it is computed. 否则,计算它。 Note that in a
declare enum
, only numeric initializers are allowed. 请注意,在
declare enum
,只允许使用数字初始值设定项。 Example: 例:
declare enum Foo {
X, // Computed
Y = 2, // Non-computed
Z, // Computed! Not 3! Careful!
Q = 1 + 1 // Error
}
Finally, members of non-declare non-const enums are always considered to be computed. 最后,始终认为非声明非const枚举的成员是计算的。 However, their initializing expressions are reduced down to constants if they're computable at compile-time.
但是,如果它们在编译时可计算,则它们的初始化表达式会减少到常量。 This means non-const enum members are never inlined (this behavior changed in TypeScript 1.5, see "Changes in TypeScript" at the bottom)
这意味着非const枚举成员永远不会内联(在TypeScript 1.5中更改了此行为,请参阅底部的“TypeScript中的更改”)
An enum declaration can have the const
modifier. 枚举声明可以有
const
修饰符。 If an enum is const
, all references to its members inlined. 如果枚举是
const
,则所有对其成员的引用都是内联的。
const enum Foo { A = 4 }
var x = Foo.A; // emitted as "var x = 4;", always
const enums do not produce a lookup object when compiled. const枚举在编译时不会生成查找对象。 For this reason, it is an error to reference
Foo
in the above code except as part of a member reference. 因此,除了作为成员引用的一部分之外,在上面的代码中引用
Foo
是错误的。 No Foo
object will be present at runtime. 运行时不会出现
Foo
对象。
If an enum declaration does not have the const
modifier, references to its members are inlined only if the member is non-computed. 如果枚举声明没有
const
修饰符,则仅当成员未计算时才会内联对其成员的引用。 A non-const, non-declare enum will produce a lookup object. 非const,非声明枚举将生成查找对象。
An important preface is that declare
in TypeScript has a very specific meaning: This object exists somewhere else . 一个重要的前言是TypeScript中的
declare
具有非常特定的含义: 该对象存在于其他地方 。 It's for describing existing objects. 它用于描述现有对象。 Using
declare
to define objects that don't actually exist can have bad consequences; 使用
declare
来定义实际不存在的对象会产生不良后果; we'll explore those later. 我们稍后会探讨这些。
A declare enum
will not emit a lookup object. declare enum
不会发出查找对象。 References to its members are inlined if those members are computed (see above on computed vs non-computed). 如果计算了这些成员,则会内联对其成员的引用(参见上面的计算与非计算)。
It's important to note that other forms of reference to a declare enum
are allowed, eg this code is not a compile error but will fail at runtime: 需要注意的是其他形式的参照是很重要的
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
This error falls under the category of "Don't lie to the compiler". 此错误属于“不要欺骗编译器”的类别。 If you don't have an object named
Foo
at runtime, don't write declare enum Foo
! 如果在运行时没有名为
Foo
的对象,请不要编写declare enum Foo
!
A declare const enum
is not different from a const enum
, except in the case of --preserveConstEnums (see below). 甲
declare const enum
不是从不同的const enum
,除了在--preserveConstEnums(见下文)的情况下。
A non-declare enum produces a lookup object if it is not const
. 如果不是
const
,则非声明枚举会生成查找对象。 Inlining is described above. 上面描述了内联。
This flag has exactly one effect: non-declare const enums will emit a lookup object. 此标志只有一个效果:非声明const枚举将发出查找对象。 Inlining is not affected.
内联不受影响。 This is useful for debugging.
这对调试很有用。
The most common mistake is to use a declare enum
when a regular enum
or const enum
would be more appropriate. 最常见的错误是当常规
enum
或const enum
更合适时使用declare enum
。 A common form is this: 一个常见的形式是:
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]);
}
Remember the golden rule: Never declare
things that don't actually exist . 记住黄金法则: 永远不要
declare
实际上不存在的东西 。 Use const enum
if you always want inlining, or enum
if you want the lookup object. 使用
const enum
,如果你总是想内联,或enum
,如果你想查找对象中。
Between TypeScript 1.4 and 1.5, there was a change in the behavior (see https://github.com/Microsoft/TypeScript/issues/2183 ) to make all members of non-declare non-const enums be treated as computed, even if they're explicitly initialized with a literal. 在TypeScript 1.4和1.5之间,行为发生了变化(请参阅https://github.com/Microsoft/TypeScript/issues/2183 ),以使非声明非const枚举的所有成员都被视为计算,即使它们是用文字明确初始化的。 This "unsplit the baby", so to speak, making the inlining behavior more predictable and more cleanly separating the concept of
const enum
from regular enum
. 这个“会合并宝贝”,可以这么说,使得内联行为更可预测和更清洁分离的概念
const enum
从正规enum
。 Prior to this change, non-computed members of non-const enums were inlined more aggressively. 在此更改之前,非const enums的非计算成员被更加积极地内联。
There are a few things going on here. 这里有一些事情发生。 Let's go case by case.
让我们逐个进行。
enum Cheese { Brie, Cheddar }
First, a plain old enum. 首先,一个普通的老枚举。 When compiled to JavaScript, this will emit a lookup table.
编译为JavaScript时,这将发出一个查找表。
The lookup table looks like this: 查找表如下所示:
var Cheese;
(function (Cheese) {
Cheese[Cheese["Brie"] = 0] = "Brie";
Cheese[Cheese["Cheddar"] = 1] = "Cheddar";
})(Cheese || (Cheese = {}));
Then when you have Cheese.Brie
in TypeScript, it emits Cheese.Brie
in JavaScript which evaluates to 0. Cheese[0]
emits Cheese[0]
and actually evaluates to "Brie"
. 然后当你在TypeScript中使用
Cheese.Brie
时,它会在JavaScript中发出Cheese.Brie
,其值为0. Cheese[0]
发出Cheese[0]
并实际评估为"Brie"
。
const enum Bread { Rye, Wheat }
No code is actually emitted for this! 实际上没有为此发出代码! Its values are inlined.
它的值是内联的。 The following emit the value 0 itself in JavaScript:
以下内容在JavaScript中发出值0:
Bread.Rye
Bread['Rye']
const enum
s' inlining might be useful for performance reasons. const enum
s'内联可能因性能原因而有用。
But what about Bread[0]
? 但是
Bread[0]
怎么样? This will error out at runtime and your compiler should catch it. 这将在运行时出错,您的编译器应该捕获它。 There's no lookup table and the compiler doesn't inline here.
没有查找表,编译器不在此处内联。
Note that in the above case, the --preserveConstEnums flag will cause Bread to emit a lookup table. 请注意,在上面的情况中, - prepareConstEnums标志将导致Bread发出查找表。 Its values will still be inlined though.
它的价值仍然会被内联。
As with other uses of declare
, declare
emits no code and expects you to have defined the actual code elsewhere. 与
declare
其他用法一样, declare
发出代码,并且希望您在其他地方定义实际代码。 This emits no lookup table: 这不会发出查找表:
declare enum Wine { Red, Wine }
Wine.Red
emits Wine.Red
in JavaScript, but there won't be any Wine lookup table to reference so it's an error unless you've defined it elsewhere. Wine.Red
在JavaScript中发出Wine.Red
,但是没有任何Wine查找表可供引用,所以除非你在别处定义它,否则它是一个错误。
This emits no lookup table: 这不会发出查找表:
declare const enum Fruit { Apple, Pear }
But it does inline! 但它确实内联!
Fruit.Apple
emits 0. But again Fruit[0]
will error out at runtime because it's not inlined and there's no lookup table. Fruit.Apple
会发出0.然后Fruit[0]
会在运行时出错,因为它没有内联,也没有查找表。
I've written this up in this playground. 我在这个操场上写了这篇文章 。 I recommend playing there to understand which TypeScript emits which JavaScript.
我建议在那里玩,以了解哪个TypeScript发出哪个JavaScript。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.