不同的枚举变体如何在 TypeScript 中工作?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/28818849/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do the different enum variants work in TypeScript?
提问by Ryan Cavanaugh
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
? What does const
or declare
mean on the declarations here?
如果我尝试Gamma
在运行时使用一个值,我会收到一个错误,因为Gamma
没有定义,但是Delta
or不是这种情况Alpha
?是什么const
或declare
在这里的声明是什么意思?
There's also a preserveConstEnums
compiler flag -- how does this interact with these?
还有一个preserveConstEnums
编译器标志——它如何与这些交互?
回答by Ryan Cavanaugh
There are four different aspects to enums in TypeScript you need to be aware of. First, some definitions:
您需要注意 TypeScript 中枚举的四个不同方面。首先,一些定义:
"lookup object"
“查找对象”
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 stringsto numbers, e.g. when writing Foo.X
or Foo['X']
, and to serve as a mapping from numbersto strings. 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"
.
我将把它称为查找对象。它的目的是双重的:用作从字符串到数字的映射,例如在编写Foo.X
or 时Foo['X']
,以及用作从数字到字符串的映射。该反向映射对于调试或记录目的很有用——您通常会拥有值0
or1
并且想要获取相应的字符串"X"
or "Y"
。
"declare"or "ambient"
“声明”或“环境”
In TypeScript, you can "declare" things that the compiler should know about, but not actually emit code for. This is useful when you have libraries like jQuery that define some object (e.g. $
) that you want type information about, but don't need any code created by the compiler. 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).
在 TypeScript 中,您可以“声明”编译器应该知道的事情,但实际上不会为其发出代码。当您有像 jQuery 这样的库定义了一些$
您想要类型信息的对象(例如),但不需要编译器创建的任何代码时,这很有用。规范和其他文档将这种方式的声明称为在“环境”上下文中;重要的是要注意.d.ts
文件中的所有声明都是“环境”的(需要显式declare
修饰符或隐式修饰符,取决于声明类型)。
"inlining"
“内联”
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 inliningbecause it sounds cooler. Sometimes you will notwant enum members to be inlined, for example because the enum value might change in a future version of the API.
规范称此为替代,我称其为内联,因为它听起来更酷。有时您不希望 enum 成员被内联,例如因为 enum 值可能会在 API 的未来版本中发生变化。
Enums, how do they work?
枚举,它们是如何工作的?
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.
让我们通过枚举的每个方面来分解它。不幸的是,这四个部分中的每一个都将引用所有其他部分的术语,因此您可能需要不止一次阅读整篇文章。
computed vs non-computed (constant)
计算与非计算(常量)
Enum members can either be computedor not. The spec calls non-computed members constant, but I'll call them non-computedto avoid confusion with const.
枚举成员可以被计算,也可以不被计算。规范将非计算成员称为constant,但我将称它们为非计算成员以避免与const混淆。
A computedenum member is one whose value is not known at compile-time. References to computed members cannot be inlined, of course. Conversely, a non-computedenum member is once whose value isknown 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 (i.e. non-computed), as the name implies. For a non-const enum, it depends on whether you're looking at an ambient(declare) enum or a non-ambient enum.
哪些枚举成员是计算的,哪些是非计算的?首先,const
枚举的所有成员都是常量(即非计算),顾名思义。对于非常量枚举,这取决于您是查看环境(声明)枚举还是非环境枚举。
A member of a declare enum
(i.e. ambient enum) is constant if and only ifit has an initializer. Otherwise, it is computed. Note that in a declare enum
, only numeric initializers are allowed. Example:
a 的成员declare enum
(即环境枚举)是常量当且仅当它有一个初始化器。否则,它被计算。请注意,在 a 中declare enum
,只允许使用数字初始值设定项。例子:
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. 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)
最后,非声明非常量枚举的成员总是被认为是被计算的。但是,如果它们在编译时是可计算的,则它们的初始化表达式将减少为常量。这意味着非常量枚举成员永远不会内联(此行为在 TypeScript 1.5 中更改,请参阅底部的“TypeScript 中的更改”)
const vs non-const
常量与非常量
const
常量
An enum declaration can have the const
modifier. If an enum is const
, allreferences to its members inlined.
枚举声明可以有const
修饰符。如果枚举是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. For this reason, it is an error to reference Foo
in the above code except as part of a member reference. No Foo
object will be present at runtime.
const 枚举在编译时不会产生查找对象。因此,Foo
在上述代码中引用是错误的,除非作为成员引用的一部分。Foo
运行时不会出现任何对象。
non-const
非常量
If an enum declaration does not have the const
modifier, references to its members are inlined only if the member is non-computed. A non-const, non-declare enum will produce a lookup object.
如果枚举声明没有const
修饰符,则仅当成员未计算时才内联对其成员的引用。一个非常量、非声明的枚举将产生一个查找对象。
declare (ambient) vs non-declare
声明(环境)与非声明
An important preface is that declare
in TypeScript has a very specific meaning: This object exists somewhere else. It's for describing existingobjects. Using declare
to define objects that don't actually exist can have bad consequences; we'll explore those later.
一个重要的前言是,declare
在 TypeScript 中有一个非常具体的含义:这个对象存在于别处。它用于描述现有对象。使用declare
定义实际上并不存在可以有不良后果的对象; 我们稍后会探讨这些。
declare
宣布
A declare enum
will not emit a lookup object. References to its members are inlined if those members are computed (see above on computed vs non-computed).
Adeclare enum
不会发出查找对象。如果这些成员被计算,则对其成员的引用是内联的(参见上文关于计算与非计算)。
It's important to note that other forms of reference to a declare enum
areallowed, e.g. this code is nota compile error but willfail 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).
Adeclare const enum
与 a 没有区别const enum
,除了 --preserveConstEnums (见下文)。
non-declare
未申报
A non-declare enum produces a lookup object if it is not const
. Inlining is described above.
如果不是,则非声明枚举会生成查找对象const
。内联如上所述。
--preserveConstEnums flag
--preserveConstEnums 标志
This flag has exactly one effect: non-declare const enums will emit a lookup object. Inlining is not affected. This is useful for debugging.
这个标志只有一个效果:未声明的 const 枚举将发出一个查找对象。内联不受影响。这对于调试很有用。
Common Errors
常见错误
The most common mistake is to use a declare enum
when a regular enum
or const enum
would be more appropriate. A common form is this:
最常见的错误是使用declare enum
when 常规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]);
}
Remember the golden rule: Never declare
things that don't actually exist. Use const enum
if you always want inlining, or enum
if you want the lookup object.
记住黄金法则:永远declare
不存在实际上不存在的事物。使用const enum
,如果你总是希望内联,或者enum
如果你想查找的对象。
Changes in TypeScript
打字稿的变化
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. 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
. Prior to this change, non-computed members of non-const enums were inlined more aggressively.
在 TypeScript 1.4 和 1.5 之间,行为发生了变化(请参阅https://github.com/Microsoft/TypeScript/issues/2183)使非声明非常量枚举的所有成员都被视为已计算,即使它们是用文字显式初始化的。这个“会合并宝贝”,可以这么说,使得内联行为更可预测和更清洁分离的概念const enum
从正规enum
。在此更改之前,非常量枚举的非计算成员被更积极地内联。
回答by Kat
There are a few things going on here. Let's go case by case.
这里发生了一些事情。让我们逐案进行。
enum
枚举
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"
.
然后,当您Cheese.Brie
在 TypeScript 中时,它会Cheese.Brie
在 JavaScript 中Cheese[0]
发出计算结果为 0.发出Cheese[0]
并实际计算为"Brie"
.
const enum
常量枚举
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]
? This will error out at runtime and your compiler should catch it. There's no lookup table and the compiler doesn't inline here.
但是呢Bread[0]
?这将在运行时出错,您的编译器应该会捕获它。没有查找表,编译器不会在此处内联。
Note that in the above case, the --preserveConstEnums flag will cause Bread to emit a lookup table. Its values will still be inlined though.
请注意,在上述情况下,--preserveConstEnums 标志将导致 Bread 发出查找表。它的值仍然会被内联。
declare enum
声明枚举
As with other uses of declare
, declare
emits no code and expects you to have defined the actual code elsewhere. This emits no lookup table:
与 的其他用途一样declare
,不declare
发出任何代码,并希望您在别处定义实际代码。这不会发出任何查找表:
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
Wine.Red
在 JavaScript 中发出,但不会有任何 Wine 查找表可供参考,因此除非您在其他地方定义它,否则这是一个错误。
declare const enum
声明常量枚举
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 thisplayground. I recommend playing there to understand which TypeScript emits which JavaScript.