简体   繁体   English

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

[英]How do the different enum variants work in TypeScript?

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 ? 如果我在运行时尝试使用Gamma中的值,我会收到一个错误,因为未定义Gamma ,但DeltaAlpha不是这种情况? What does const or declare mean on the declarations here? constdeclaredeclare含义是什么?

There's also a preserveConstEnums compiler flag -- how does this interact with these? 还有一个preserveConstEnums编译器标志 - 这与这些标志如何相互作用?

There are four different aspects to enums in TypeScript you need to be aware of. 您需要注意TypeScript中的枚举有四个不同的方面。 First, some definitions: 首先,一些定义:

"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 strings to numbers , eg when writing Foo.X or Foo['X'] , and to serve as a mapping from numbers to strings . 它的目的是双重的:用作从字符串数字的映射,例如在写入Foo.XFoo['X'] ,用作从数字字符串的映射。 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" . 反向映射对于调试或记录目的很有用 - 您通常会得到值01并希望获得相应的字符串"X""Y"

"declare" or " ambient " “声明”或“ 环境

In TypeScript, you can "declare" things that the compiler should know about, but not actually emit code for. 在TypeScript中,您可以“声明”编译器应该知道的内容,但实际上不会为其发出代码。 This is useful when you have libraries like jQuery that define some object (eg $ ) that you want type information about, but don't need any code created by the compiler. 当你有像jQuery这样的库定义一些你想要类型信息但不需要编译器创建的代码的对象(例如$ )时,这很有用。 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). 重要的是要注意.d.ts文件中的所有声明都是“环境”(需要显式declare修饰符或隐式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 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. 有时您希望枚举枚举成员,例如因为枚举值可能会在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 . 规范将非计算成员调用为常量 ,但我将其称为非计算,以避免与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 (ie non-computed), as the name implies. 首先, const enum的所有成员都是常量(即非计算的),顾名思义。 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 (ie ambient enum) is constant if and only if it has an initializer. declare enum (即环境枚举)的成员当且仅当它具有初始化器时才是常量。 Otherwise, it is computed. 否则,计算它。 Note that in a declare enum , only numeric initializers are allowed. 请注意,在declare enum ,只允许使用数字初始值设定项。 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. 最后,始终认为非声明非const枚举的成员是计算的。 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枚举成员永远不会内联(在TypeScript 1.5中更改了此行为,请参阅底部的“TypeScript中的更改”)

const vs non-const const与非const

const 常量

An enum declaration can have the const modifier. 枚举声明可以有const修饰符。 If an enum is const , all references to its members inlined. 如果枚举是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. const枚举在编译时不会生成查找对象。 For this reason, it is an error to reference Foo in the above code except as part of a member reference. 因此,除了作为成员引用的一部分之外,在上面的代码中引用Foo是错误的。 No Foo object will be present at runtime. 运行时不会出现Foo对象。

non-const 非const

If an enum declaration does not have the const modifier, references to its members are inlined only if the member is non-computed. 如果枚举声明没有const修饰符,则仅当成员未计算时才会内联对其成员的引用。 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 . 一个重要的前言是TypeScript中的declare具有非常特定的含义: 该对象存在于其他地方 It's for describing existing objects. 它用于描述现有对象。 Using declare to define objects that don't actually exist can have bad consequences; 使用declare来定义实际不存在的对象会产生不良后果; we'll explore those later. 我们稍后会探讨这些。

declare 宣布

A declare enum will not emit a lookup object. declare enum不会发出查找对象。 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, eg this code is not a compile error but will fail 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). declare const enum不是从不同的const enum ,除了在--preserveConstEnums(见下文)的情况下。

non-declare 非申报

A non-declare enum produces a lookup object if it is not const . 如果不是const ,则非声明枚举会生成查找对象。 Inlining is described above. 上面描述了内联。

--preserveConstEnums flag --preserveConstEnums标志

This flag has exactly one effect: non-declare const enums will emit a lookup object. 此标志只有一个效果:非声明const枚举将发出查找对象。 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. 最常见的错误是当常规enumconst enum更合适时使用declare enum 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 . 记住黄金法则: 永远不要declare实际上不存在的东西 Use const enum if you always want inlining, or enum if you want the lookup object. 使用const enum ,如果你总是想内联,或enum ,如果你想查找对象中。


Changes in TypeScript 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. 在TypeScript 1.4和1.5之间,行为发生了变化(请参阅https://github.com/Microsoft/TypeScript/issues/2183 ),以使非声明非const枚举的所有成员都被视为计算,即使它们是用文字明确初始化的。 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 . 这个“会合并宝贝”,可以这么说,使得内联行为更可预测和更清洁分离的概念const enum从正规enum Prior to this change, non-computed members of non-const enums were inlined more aggressively. 在此更改之前,非const enums的非计算成员被更加积极地内联。

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" . 然后当你在TypeScript中使用Cheese.Brie时,它会在JavaScript中发出Cheese.Brie ,其值为0. Cheese[0]发出Cheese[0]并实际评估为"Brie"

const enum 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] ? 但是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. 没有查找表,编译器不在此处内联。

Note that in the above case, the --preserveConstEnums flag will cause Bread to emit a lookup table. 请注意,在上面的情况中, - prepareConstEnums标志将导致Bread发出查找表。 Its values will still be inlined though. 它的价值仍然会被内联。

declare enum 声明枚举

As with other uses of declare , declare emits no code and expects you to have defined the actual code elsewhere. declare其他用法一样, declare发出代码,并且希望您在其他地方定义实际代码。 This emits no lookup table: 这不会发出查找表:

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在JavaScript中发出Wine.Red ,但是没有任何Wine查找表可供引用,所以除非你在别处定义它,否则它是一个错误。

declare const enum 声明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 this playground. 我在这个操场上写了这篇文章 I recommend playing there to understand which TypeScript emits which JavaScript. 我建议在那里玩,以了解哪个TypeScript发出哪个JavaScript。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM