不同的枚举变量在TypeScript中如何工作?


116

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则会收到错误消息,因为未定义,但这不是for DeltaAlpha?的情况。是什么constdeclare在这里的声明是什么意思?

还有一个preserveConstEnums编译器标志-它如何与它们交互?


1
我刚刚写了一篇关于此的文章,尽管它与将const与非const枚举进行比较有更多关系
joelmdev

Answers:


247

您需要了解TypeScript中枚举的四个不同方面。首先,一些定义:

“查找对象”

如果您编写此枚举:

enum Foo { X, Y }

TypeScript将发出以下对象:

var Foo;
(function (Foo) {
    Foo[Foo["X"] = 0] = "X";
    Foo[Foo["Y"] = 1] = "Y";
})(Foo || (Foo = {}));

我将其称为查阅对象。它的目的是双重的:例如,当编写或时,用作从字符串数字的映射,以及用作从数字字符串的映射。Foo.XFoo['X']。反向映射对于调试或记录日志很有用-您通常会拥有值0or 1并想要获取相应的字符串"X""Y"

“宣布”或“ 环境

在TypeScript中,您可以“声明”编译器应了解的内容,但实际上并不为其发出代码。当您具有类似jQuery的库来定义某些对象(例如,$您要输入其类型信息但不需要编译器创建的代码的)时,。规范和其他文档将这种声明称为“环境”上下文。重要的是要注意,.d.ts文件中的所有声明都是“环境”的(declare根据声明类型需要显式修饰符还是隐式修饰符)。

“内联”

出于性能和代码大小的原因,通常最好在编译时将枚举成员的引用替换为其数值等效项:

enum Foo { X = 4 }
var y = Foo.X; // emits "var y = 4";

规范将其称为替代,我将其称为内联,因为它听起来更酷。有时候你会希望内联枚举成员,例如,因为在API的未来版本中枚举值可能会更改。


枚举,它们如何工作?

让我们按枚举的每个方面对其进行细分。不幸的是,这四个部分中的每一个都将引用其他所有部分中的术语,因此您可能需要多次阅读整个内容。

计算与非计算(常数)

枚举成员可以计算也可以不计算。该规范要求非计算成员不变,但我会打电话给他们非计算,以避免混乱与常量

计算枚举成员是一个其值在编译时是未知的。当然,不能内联对计算所得成员的引用。相反,非计算的枚举成员是一次其值在编译时已知的。始终内联对非计算成员的引用。

哪些枚举成员是计算的,哪些不是计算的?首先,const顾名思义,枚举的所有成员都是常量(即未计算)。对于非常量枚举,取决于您是否正在查看环境(声明)枚举还是非环境枚举。

declare enum(即环境枚举)的成员只有且具有初始化器时才是常数。否则,将对其进行计算。请注意,在中declare enum,仅允许使用数字初始值设定项。例:

declare enum Foo {
    X, // Computed
    Y = 2, // Non-computed
    Z, // Computed! Not 3! Careful!
    Q = 1 + 1 // Error
}

最后,非声明非常量枚举的成员始终被视为已计算。但是,如果它们的初始化表达式在编译时是可计算的,则将它们简化为常量。这意味着永远不会内联非const枚举成员(此行为在TypeScript 1.5中已更改,请参阅底部的“ TypeScript中的更改”)

const vs非const

const

枚举声明可以具有const修饰符。如果枚举为const,则对所有成员的引用都将内联。

const enum Foo { A = 4 }
var x = Foo.A; // emitted as "var x = 4;", always

const枚举在编译时不会产生查找对象。因此,Foo在上述代码中进行引用是错误的,只是作为成员引用的一部分。没有Foo在运行时将不存在对象。

非常量

如果枚举声明不包含const修饰符,则仅在非计算成员的情况下才内联对其成员的引用。一个非常量,非声明枚举将产生一个查找对象。

声明(环境)与未声明

一个重要的序言是,declare在TypeScript中具有非常特殊的含义:该对象存在于其他地方。用于描述现有对象。使用declare定义实际上不存在的对象可能会带来严重的后果。我们稍后再探讨。

宣布

一个 declare enum不会发出查找对象。如果计算了这些成员,则对其成员的引用会内联(请参见上文关于计算与非计算的内容)。

需要注意的是其他形式的参照是很重要的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

A declare const enum与-没什么不同const enum,除了--preserveConstEnums(请参见下文)。

未声明

如果不是,则未声明的枚举会生成查找对象const。内联如上所述。

--preserveConstEnums标志

该标志仅具有一种作用:非声明const枚举将发出一个查找对象。内联不受影响。这对于调试很有用。


常见错误

最常见的错误是declare enum在常规时enumconst enum更合适时使用a 。常见的形式是这样的:

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,如果你总是希望内联,或者enum如果你想查找的对象。


TypeScript中的更改

在TypeScript 1.4和1.5之间,行为发生了变化(请参阅https://github.com/Microsoft/TypeScript/issues/2183),即使未声明的非const枚举的所有成员都被视为已计算的,即使它们是用文字显式初始化的。可以说,这种“未分裂的婴儿”使内联行为更可预测,并且更清晰地将“ const enum常规”概念与常规概念分开enum。在进行此更改之前,会更积极地内联非常量枚举的非计算成员。


6
一个非常棒的答案。它为我清除了很多东西,而不仅仅是枚举。
克拉克

1
我希望我可以多次投票给你...不知道那项重大变革。在正确的语义版本控制中,这可以被认为是对主要版本的颠簸:-/
mfeineis,2015年

enum各种类型的比较很有帮助,谢谢!
Marius Schulz 2015年

@Ryan这非常有帮助,谢谢!现在,我们只需要Web Essentials 2015即可const为声明的枚举类型生成适当的代码。
styfle

19
这个答案似乎非常详细地解释了1.4中的情况,然后最后说:“但是1.5改变了所有情况,现在更简单了。” 假设我理解正确的事情,这个组织是会得到越来越多的不合适的,这个答案变老:我强烈建议把简单的,目前的情况,只有说:“但是如果你使用1.4或更早版本,事情比较复杂。”
KRyan 2015年

33

这里发生了一些事情。让我们逐案进行。

枚举

enum Cheese { Brie, Cheddar }

首先,一个普通的旧枚举。当编译为JavaScript时,它将发出一个查找表。

查找表如下所示:

var Cheese;
(function (Cheese) {
    Cheese[Cheese["Brie"] = 0] = "Brie";
    Cheese[Cheese["Cheddar"] = 1] = "Cheddar";
})(Cheese || (Cheese = {}));

然后,当你Cheese.Brie在打字稿,它发出Cheese.Brie的JavaScript的计算结果为0 Cheese[0]发出Cheese[0]和实际计算结果为"Brie"

常量枚举

const enum Bread { Rye, Wheat }

实际上没有为此发出代码!其值是内联的。以下代码在JavaScript中自身发出值0:

Bread.Rye
Bread['Rye']

const enum出于性能原因,s的内联可能很有用。

但是呢Bread[0]?这将在运行时出错,并且编译器应该捕获它。没有查找表,并且编译器未在此内联。

请注意,在上述情况下,--preserveConstEnums标志将使Bread发出查找表。它的值仍将内联。

声明枚举

与的其他用法一样declare,它declare不会发出任何代码,并且希望您在其他地方定义了实际的代码。这不会发出查找表:

declare enum Wine { Red, Wine }

Wine.RedWine.Red在JavaScript中发出,但是不会引用任何Wine查找表,因此除非您在其他地方定义了它,否则这是错误的。

声明const枚举

这不会发出查找表:

declare const enum Fruit { Apple, Pear }

但这确实是内联的!Fruit.Apple发出0。但是Fruit[0]由于没有内联并且没有查找表,因此在运行时也会再次出错。

我写这件事在这个操场。我建议在那里玩,以了解哪种TypeScript发出哪种JavaScript。


1
我建议更新此答案:从Typescript 3.3.3开始,Bread[0]引发编译器错误:“只能使用字符串文字访问const枚举成员。”
chharvey

1
嗯...和答案说的有什么不同吗?“但是,Bread [0]呢?在运行时会出错,您的编译器应该捕获它。没有查找表,并且编译器不在此内联。”
凯特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.