不同的枚举变体如何在TypeScript中工作? [英] How do the different enum variants work in TypeScript?
问题描述
枚举Alpha {X,Y,Z}
const enum Beta {X,Y,Z}
声明枚举Gamma {X,Y,Z}
声明const enum Delta {X,Y,Z}
如果我在运行时尝试使用 Gamma
中的值,我会收到错误,因为 Gamma
未定义,但是 Delta
或 Alpha
? const
或声明
在这里的声明是什么意思?
还有一个 preserveConstEnums
编译器标志 - 它与这些相互作用如何?
您需要注意的TypeScript中的枚举有四个不同的方面。首先,一些定义:
查找对象
如果你写这个枚举:
枚举Foo {X,Y}
TypeScript将发出以下对象:
var Foo;
(function(Foo){
Foo [Foo [X] = 0] =X;
Foo [Foo [Y] = 1] =Y
})(Foo ||(Foo = {}));
我将其称为查找对象。其目的是双重的:用作从字符串到数字的映射,例如当写 Foo.X
或 Foo ['X']
,并作为从数字的映射到字符串。该反向映射对于调试或记录目的很有用 - 您通常会有 0
或 1
的值,并且希望获取相应的字符串X
或Y
。
>
在TypeScript中,您可以声明编译器应该知道,但实际上不会发出代码。当您有类似jQuery的库定义某些对象(例如 $
)时,您需要键入信息,但不需要编译器创建的任何代码,这是非常有用的。规范和其他文档指的是以这种方式处于环境上下文中的声明;重要的是要注意, .d.ts
文件中的所有声明都是环境(要求显式声明
/>> $ / $>
内联
由于性能和代码大小的原因,编译时通常会优先使用枚举成员替换其数值等价物:
enum Foo {X = 4}
var y = Foo.X; //发出var y = 4;
规范调用这个替换,我将其称为内联因为听起来更冷。有时您将不会希望枚举成员被内联,例如因为枚举值可能会在将来的API版本中更改。
枚举,它们如何工作?
让我们根据枚举的每个方面打破这一点。不幸的是,这四个部分中的每一个都将参考所有其他部分的内容,所以你可能需要多次阅读这个整体。
计算vs非计算(常数)
枚举成员可以是计算。该规范调用非计算成员常量,但我会称之为非计算,以避免与 混淆
计算的枚举成员是一个在编译时不知道其值的成员。当然,对计算成员的引用不能内联。相反,未编译的枚举成员在编译时已经知道其值是。对非计算成员的引用总是内联的。
哪些枚举成员是计算的,哪些不计算?首先,顾名思义, const
枚举的所有成员都是常量(即非计算)。对于非常量枚举,这取决于您是否正在查看环境(declare)枚举或非环境枚举。
当且仅当它具有一个初始化器时,声明枚举
(即环境枚举)的成员是常量。否则,它被计算。请注意,在声明枚举
中,只允许使用数字初始值设置。示例:
声明枚举Foo {
X,//计算
Y = 2,//非计算
Z,//计算!不是3!小心!
Q = 1 + 1 //错误
}
最后,不声明非常量枚举总是被认为是计算的。然而,如果在编译时可以计算它们的初始化表达式,则它们将被缩减为常量。这意味着非常量枚举成员永远不会内联(此行为在TypeScript 1.5中更改,请参阅底部的TypeScript中的更改)
const vs non-const < h2>
const
枚举声明可以有 const
修饰符。如果枚举是 const
,所有引用其成员内联。
const enum Foo {A = 4}
var x = Foo.A; //发出为var x = 4;,总是
const枚举不产生查找对象编译时。因此,在上述代码中引用 Foo
是错误的,但作为成员引用的一部分。 $ Foo
对象将在运行时出现。
non-const
如果枚举声明中没有 const
修饰符,则仅当成员未计算时,才引用其成员。一个非常量的非声明枚举将产生一个查找对象。
declare(ambient)vs non-declare
一个重要的前言是TypeScript中的声明
具有非常具体的含义:此对象存在于其他地方。它用于描述现有的对象。使用声明
定义不存在的对象可能会产生不良后果;
声明
A 声明枚举
不会发出查找对象。如果这些成员被计算(参见上面的计算与非计算),则引用它的成员。
重要的是要注意其他形式的引用 declare枚举
是允许的,例如这个代码不是一个编译错误,但是将在运行时失败:
//注意:假设没有其他文件实际上在运行时创建了一个Foo var
declare枚举Foo {Bar}
var s ='Bar';
var b = Foo [s]; //失败
此错误属于不要骗到编译器的类别。如果在运行时没有名为 Foo
的对象,请不要写声明枚举Foo
!
一个声明const enum
与 const enum
不同,除非是--preserveConstEnums(见下文)。
非声明
非声明枚举产生一个查找对象,如果它不是 const
。内联如上所述。
- preserveConstEnums标志
此标志只有一个效果:non-declare const枚举将发出一个查找对象。内联不受影响。这对调试非常有用。
常见错误
最常见的错误是在常规的枚举
或 const enum
中使用声明枚举
code>会更合适。一个常见的形式是:
module MyModule {
//声明此枚举与declare存在,但它没有...
export declare枚举Lies {
Foo = 0,
Bar = 1
}
var x = Lies.Foo; //依赖于内联
}
模块SomeOtherCode {
// x在运行时结束为'undefined'
import x = MyModule.Lies;
//尝试使用查找对象,它应该存在
//运行时错误,canot读取未定义的
console.log(x [x.Foo])的属性0 ;
}
记住黄金规则:从不声明
不实际存在的东西。如果您想要查找对象,请使用 const enum
,如果您希望内联,或枚举
TypeScript中的更改
在TypeScript 1.4和1.5之间,行为(请参阅 https://github.com/Microsoft/TypeScript/issues/2183 )使非声明非常量枚举的所有成员都被视为已计算,即使它们是使用文字显式初始化的。可以这么说,使内联行为更可预测,更干净地将常规枚举 c / c> c
TypeScript has a bunch of different ways to define an enum:
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?
There's also a preserveConstEnums
compiler flag -- how does this interact with these?
There are four different aspects to enums in TypeScript you need to be aware of. First, some definitions:
"lookup object"
If you write this enum:
enum Foo { X, Y }
TypeScript will emit the following object:
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, e.g. when writing Foo.X
or Foo['X']
, and to serve as a mapping from numbers to 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"
.
"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).
"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 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.
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 computed or not. The spec calls non-computed members constant, but I'll call them non-computed to avoid confusion with 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 (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.
A member of a declare enum
(i.e. ambient enum) is constant if and only if it has an initializer. Otherwise, it is computed. Note that in a declare enum
, only numeric initializers are allowed. 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. 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 vs non-const
const
An enum declaration can have the const
modifier. If an enum is const
, all references to its members inlined.
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.
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.
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 existing objects. Using declare
to define objects that don't actually exist can have bad consequences; we'll explore those later.
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).
It's important to note that other forms of reference to a declare enum
are allowed, e.g. this code is not a compile error but will fail at runtime:
// 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
!
A declare const enum
is not different from a const enum
, except in the case of --preserveConstEnums (see below).
non-declare
A non-declare enum produces a lookup object if it is not const
. Inlining is described above.
--preserveConstEnums flag
This flag has exactly one effect: non-declare const enums will emit a lookup object. Inlining is not affected. This is useful for debugging.
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:
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.
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中工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!