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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28818849/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 06:36:02  来源:igfitidea点击:

How do the different enum variants work in TypeScript?

enumstypescript

提问by Ryan Cavanaugh

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 Gammaat runtime, I get an error because Gammais not defined, but that's not the case for Deltaor Alpha? What does constor declaremean on the declarations here?

如果我尝试Gamma在运行时使用一个值,我会收到一个错误,因为Gamma没有定义,但是Deltaor不是这种情况Alpha?是什么constdeclare在这里的声明是什么意思?

There's also a preserveConstEnumscompiler flag -- how does this interact with these?

还有一个preserveConstEnums编译器标志——它如何与这些交互?

回答by Ryan Cavanaugh

There are four different aspects to enums in TypeScript you need to be aware of. First, some definitions:

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

"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 stringsto numbers, e.g. when writing Foo.Xor Foo['X'], and to serve as a mapping from numbersto strings. That reverse mapping is useful for debugging or logging purposes -- you will often have the value 0or 1and want to get the corresponding string "X"or "Y".

我将把它称为查找对象。它的目的是双重的:用作从字符串数字的映射,例如在编写Foo.Xor 时Foo['X'],以及用作从数字字符串的映射。该反向映射对于调试或记录目的很有用——您通常会拥有值0or1并且想要获取相应的字符串"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.tsfile are "ambient" (either requiring an explicit declaremodifier or having it implicitly, depending on the declaration type).

在 TypeScript 中,您可以“声明”编译器应该知道的事情,但实际上不会为其发出代码。当您有像 jQuery 这样的库定义了一些$您想要类型信息的对象(例如),但不需要编译器创建的任何代码时,这很有用。规范和其他文档将这种方式的声明称为在“环境”上下文中;重要的是要注意.d.ts文件中的所有声明都是“环境”的(需要显式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 inliningbecause it sounds cooler. Sometimes you will notwant enum members to be inlined, for example because the enum value might change in a future version of the API.

规范称此为替代,我称其为内联,因为它听起来更酷。有时您希望 enum 成员被内联,例如因为 enum 值可能会在 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 computedor not. The spec calls non-computed members constant, but I'll call them non-computedto avoid confusion with const.

枚举成员可以被计算,也可以不被计算。规范将非计算成员称为constant,但我将称它们为非计算成员以避免与const混淆。

A computedenum member is one whose value is not known at compile-time. References to computed members cannot be inlined, of course. Conversely, a non-computedenum member is once whose value isknown 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 constenum 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.

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

A member of a declare enum(i.e. ambient enum) is constant if and only ifit has an initializer. Otherwise, it is computed. Note that in a declare enum, only numeric initializers are allowed. Example:

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

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)

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

const vs non-const

常量与非常量

const

常量

An enum declaration can have the constmodifier. If an enum is const, allreferences to its members inlined.

枚举声明可以有const修饰符。如果枚举是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. For this reason, it is an error to reference Fooin the above code except as part of a member reference. No Fooobject will be present at runtime.

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

non-const

非常量

If an enum declaration does not have the constmodifier, references to its members are inlined only if the member is non-computed. A non-const, non-declare enum will produce a lookup object.

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

declare (ambient) vs non-declare

声明(环境)与非声明

An important preface is that declarein TypeScript has a very specific meaning: This object exists somewhere else. It's for describing existingobjects. Using declareto define objects that don't actually exist can have bad consequences; we'll explore those later.

一个重要的前言是,declare在 TypeScript 中有一个非常具体的含义:这个对象存在于别处。它用于描述现有对象。使用declare定义实际上并不存在可以有不良后果的对象; 我们稍后会探讨这些。

declare

宣布

A declare enumwill not emit a lookup object. References to its members are inlined if those members are computed (see above on computed vs non-computed).

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

It's important to note that other forms of reference to a declare enumareallowed, e.g. this code is nota compile error but willfail 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 Fooat runtime, don't write declare enum Foo!

此错误属于“不要对编译器撒谎”的类别。如果你没有Foo在运行时命名的对象,不要写declare enum Foo

A declare const enumis not different from a const enum, except in the case of --preserveConstEnums (see below).

Adeclare const enum与 a 没有区别const enum,除了 --preserveConstEnums (见下文)。

non-declare

未申报

A non-declare enum produces a lookup object if it is not const. Inlining is described above.

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

--preserveConstEnums flag

--preserveConstEnums 标志

This flag has exactly one effect: non-declare const enums will emit a lookup object. Inlining is not affected. This is useful for debugging.

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



Common Errors

常见错误

The most common mistake is to use a declare enumwhen a regular enumor const enumwould be more appropriate. A common form is this:

最常见的错误是使用declare enumwhen 常规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]);
}

Remember the golden rule: Never declarethings that don't actually exist. Use const enumif you always want inlining, or enumif you want the lookup object.

记住黄金法则:永远declare不存在实际上不存在的事物。使用const enum,如果你总是希望内联,或者enum如果你想查找的对象。



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 enumfrom regular enum. Prior to this change, non-computed members of non-const enums were inlined more aggressively.

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

回答by Kat

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.Briein TypeScript, it emits Cheese.Briein JavaScript which evaluates to 0. Cheese[0]emits Cheese[0]and actually evaluates to "Brie".

然后,当您Cheese.Brie在 TypeScript 中时,它会Cheese.Brie在 JavaScript 中Cheese[0]发出计算结果为 0.发出Cheese[0]并实际计算为"Brie".

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 enums' inlining might be useful for performance reasons.

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

But what about 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.

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

Note that in the above case, the --preserveConstEnums flag will cause Bread to emit a lookup table. Its values will still be inlined though.

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

declare enum

声明枚举

As with other uses of declare, declareemits no code and expects you to have defined the actual code elsewhere. This emits no lookup table:

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

declare enum Wine { Red, Wine }

Wine.Redemits Wine.Redin JavaScript, but there won't be any Wine lookup table to reference so it's an error unless you've defined it elsewhere.

Wine.RedWine.Red在 JavaScript 中发出,但不会有任何 Wine 查找表可供参考,因此除非您在其他地方定义它,否则这是一个错误。

declare const enum

声明常量枚举

This emits no lookup table:

这不会发出任何查找表:

declare const enum Fruit { Apple, Pear }

But it does inline! Fruit.Appleemits 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 thisplayground. I recommend playing there to understand which TypeScript emits which JavaScript.

我已经在这个操场上写下了这个。我建议在那里玩以了解哪个 TypeScript 发出哪个 JavaScript。