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

Rya*_*ugh 94 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 }
Run Code Online (Sandbox Code Playgroud)

如果我尝试从使用的值Gamma在运行时,我得到一个错误,因为Gamma没有定义,但事实并非案例DeltaAlpha?这里的声明是什么constdeclare意味着什么?

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

Rya*_*ugh 207

您需要注意TypeScript中的枚举有四个不同的方面.首先,一些定义:

"查找对象"

如果你写这个枚举:

enum Foo { X, Y }
Run Code Online (Sandbox Code Playgroud)

TypeScript将发出以下对象:

var Foo;
(function (Foo) {
    Foo[Foo["X"] = 0] = "X";
    Foo[Foo["Y"] = 1] = "Y";
})(Foo || (Foo = {}));
Run Code Online (Sandbox Code Playgroud)

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

"声明"或" 环境 "

在TypeScript中,您可以"声明"编译器应该知道的内容,但实际上不会为其发出代码.当你有像jQuery这样的库来定义一些$你需要类型信息但不需要编译器创建的代码的对象(例如)时,这很有用.规范和其他文档将这种声明称为处于"环境"上下文中; 重要的是要注意.d.ts文件中的所有声明都是"环境"(需要显式declare修饰符或隐式修改它,具体取决于声明类型).

"内联"

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

enum Foo { X = 4 }
var y = Foo.X; // emits "var y = 4";
Run Code Online (Sandbox Code Playgroud)

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


枚举,它们如何运作?

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

计算与非计算(常数)

枚举成员可以是否可以计算.规范将非计算成员调用为常量,但我将其称为非计算,以避免与const混淆.

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

哪些枚举成员是计算的,哪些是非计算的?首先,const如名称所示,枚举的所有成员都是常量(即非计算的).对于非常量枚举,它取决于您是在查看环境(声明)枚举还是非环境枚举.

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

declare enum Foo {
    X, // Computed
    Y = 2, // Non-computed
    Z, // Computed! Not 3! Careful!
    Q = 1 + 1 // Error
}
Run Code Online (Sandbox Code Playgroud)

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

const与非const

常量

枚举声明可以包含const修饰符.如果枚举是const,所有对其成员的引用都是内联的.

const enum Foo { A = 4 }
var x = Foo.A; // emitted as "var x = 4;", always
Run Code Online (Sandbox Code Playgroud)

const枚举在编译时不会生成查找对象.因此,Foo除了作为成员引用的一部分之外,在上面的代码中引用是错误的.Foo运行时不会出现任何对象.

非const

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

声明(环境)与非声明

一个重要的前言是,declareTypeScript具有非常特殊的含义:该对象存在于其他地方.它用于描述现有对象.使用declare定义实际上并不存在可以有不良后果的对象; 我们稍后会探讨这些.

宣布

A 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
Run Code Online (Sandbox Code Playgroud)

此错误属于"不要欺骗编译器"的类别.如果您没有Foo在运行时命名的对象,请不要写declare enum Foo!

除了--preserveConstEnums(见下文)之外,A declare const enum与a没有区别const enum.

非申报

如果不是,则非声明枚举会生成查找对象const.上面描述了内联.

--preserveConstEnums标志

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


常见错误

最常见的错误是使用declare enum常规enumconst 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]);
}
Run Code Online (Sandbox Code Playgroud)

记住黄金法则:绝不declare存在实际存在的东西.使用const enum,如果你总是希望内联,或者enum如果你想查找的对象.


TypeScript的变化

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

  • 这个答案似乎详细解释了1.4中的情况,然后在最后它说"但1.5改变了这一切,现在它变得更加简单."假设我理解正确,这个组织将会越来越多这个答案变得越来越不合适了:我强烈建议把更简单的当前情况*放在第一个*,然后只有*之后说*"但如果你使用的是1.4或更早,事情就会复杂一些." (14认同)
  • 一个非常棒的答案.它为我清理了很多东西,而不仅仅是枚举. (5认同)

Kat*_*Kat 20

这里有一些事情发生.让我们逐个进行.

枚举

enum Cheese { Brie, Cheddar }
Run Code Online (Sandbox Code Playgroud)

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

查找表如下所示:

var Cheese;
(function (Cheese) {
    Cheese[Cheese["Brie"] = 0] = "Brie";
    Cheese[Cheese["Cheddar"] = 1] = "Cheddar";
})(Cheese || (Cheese = {}));
Run Code Online (Sandbox Code Playgroud)

然后,当你使用Cheese.BrieTypeScript时,它会Cheese.Brie在JavaScript中发出,其值为0. Cheese[0]发出Cheese[0]并实际求值为"Brie".

const enum

const enum Bread { Rye, Wheat }
Run Code Online (Sandbox Code Playgroud)

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

Bread.Rye
Bread['Rye']
Run Code Online (Sandbox Code Playgroud)

const enum由于性能原因,内联可能很有用.

但那怎么样Bread[0]?这将在运行时出错,您的编译器应该捕获它.没有查找表,编译器不在此处内联.

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

声明枚举

与其他用途一样declare,不declare发出代码,并希望您在其他地方定义实际代码.这不会发出查找表:

declare enum Wine { Red, Wine }
Run Code Online (Sandbox Code Playgroud)

Wine.RedWine.Red在JavaScript中发出,但是没有任何Wine查找表可供引用,所以除非你在别处定义它,否则它是一个错误.

声明const enum

这不会发出查找表:

declare const enum Fruit { Apple, Pear }
Run Code Online (Sandbox Code Playgroud)

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

我在这个操场上写了这篇文章.我建议在那里玩,以了解哪个TypeScript发出哪个JavaScript.