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

时间:2015-03-02 20:26:19

标签: enums typescript

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或{的情况并非如此{1}}? Alphaconst对声明的含义是什么?

还有一个declare编译器标志 - 这与这些标志如何相互作用?

2 个答案:

答案 0 :(得分:185)

您需要注意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']时,用作数字字符串的映射。反向映射对于调试或日志记录非常有用 - 您通常会使用值01并希望获得相应的字符串"X""Y"

"声明" 或" 环境"

在TypeScript中,您可以"声明"编译器应该知道的事情,但实际上不会发出代码。当你有像jQuery这样的库定义一些你想要类型信息的对象(例如$)时,这很有用,但是不需要编译器创建的任何代码。规范和其他文档指的是这样做的声明是在" ambient"上下文;值得注意的是,.d.ts文件中的所有声明都是"环境" (要么需要显式的declare修饰符,要么隐式使用它,具体取决于声明类型。)

"内联"

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

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

规范称为替换,我将其称为内联,因为它听起来更酷。有时你会想要枚举枚举成员,例如因为枚举值可能会在未来版本的API中发生变化。


枚举,它们如何运作?

让我们通过枚举的每个方面来打破这个问题。不幸的是,这四个部分中的每一部分都将引用所有其他部分中的术语,因此您可能需要不止一次地阅读这一部分。

计算与非计算(常数)

枚举成员可以计算。规范调用非计算成员常量,但我会将它们称为非计算,以避免与 const 混淆。

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

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

当且仅当具有初始化程序时,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和#34中的更改;在底部)

const vs non-const

常量

枚举声明可以包含const修饰符。如果枚举为const,则所有引用其内联成员。

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

const enums在编译时不会生成查找对象。因此,在上述代码中引用Foo是错误的,除非作为成员引用的一部分。

非const

如果枚举声明没有Foo修饰符,则仅当成员未计算时才会内联对其成员的引用。非const,非声明枚举将生成查找对象。

声明(环境)与非声明

一个重要的前言是TypeScript中的const具有非常特定的含义:此对象存在于其他地方。它用于描述现有的对象。使用declare来定义实际不存在的对象可能会产生不良后果;我们稍后会探讨这些。

声明

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 Foodeclare const enum没有区别,但--preserveConstEnums(见下文)除外。

非声明

非声明枚举产生查找对象(如果它不是const enum)。上面描述了内联。

- preserveConstEnums标志

此标志只有一个效果:非声明const枚举将发出查找对象。内联不受影响。这对调试很有用。


常见错误

最常见的错误是在常规constdeclare enum更合适时使用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]); } 不存在的事情。如果您一直想要内联,请使用declare;如果您想要查找对象,请使用const enum


TypeScript中的更改

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

答案 1 :(得分:14)

这里有一些事情发生。让我们逐个讨论。

枚举

enum Cheese { Brie, Cheddar }

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

查找表如下所示:

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

然后当你在TypeScript中拥有Cheese.Brie时,它会在JavaScript中发出Cheese.Brie,其评估为0. Cheese[0]会发出Cheese[0]并实际评估为"Brie"

const enum

const enum Bread { Rye, Wheat }

实际上没有为此发出任何代码!它的值是内联的。以下内容在JavaScript中发出值0:

Bread.Rye
Bread['Rye']

const enum S'内联可能对性能原因有用。

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

请注意,在上述情况下, - prepareConstEnums标志将导致Bread发出查找表。它的值仍然会被内联。

声明枚举

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

declare enum Wine { Red, Wine }

Wine.Red在JavaScript中发出Wine.Red,但不会引用任何Wine查找表,因此除非您已将其定义在其他位置,否则它会出错。< / p>

声明const enum

这不会发出查找表:

declare const enum Fruit { Apple, Pear }

但它确实内联! Fruit.Apple会发出0.但Fruit[0]会在运行时再次出错,因为它没有内联,也没有查找表。

我已经在this游乐场写了这篇文章。我建议在那里玩,以了解哪个TypeScript发出哪个JavaScript。