不同的枚举变体如何在TypeScript中工作? [英] How do the different enum variants work in TypeScript?

查看:126
本文介绍了不同的枚举变体如何在TypeScript中工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

 枚举Alpha {X,Y,Z} 
const enum Beta {X,Y,Z}
声明枚举Gamma {X,Y,Z}
声明const enum Delta {X,Y,Z}

如果我在运行时尝试使用 Gamma 中的值,我会收到错误,因为 Gamma 未定义,但是 Delta Alpha const 声明在这里的声明是什么意思?



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

解决方案

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



查找对象



如果你写这个枚举:

 枚举Foo {X,Y} 

TypeScript将发出以下对象:

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

我将其称为查找对象。其目的是双重的:用作从字符串数字的映射,例如当写 Foo.X Foo ['X'] ,并作为从数字的映射字符串。该反向映射对于调试或记录目的很有用 - 您通常会有 0 1 的值,并且希望获取相应的字符串XY



>

在TypeScript中,您可以声明编译器应该知道,但实际上不会发出代码。当您有类似jQuery的库定义某些对象(例如 $ )时,您需要键入信息,但不需要编译器创建的任何代码,这是非常有用的。规范和其他文档指的是以这种方式处于环境上下文中的声明;重要的是要注意, .d.ts 文件中的所有声明都是环境(要求显式声明 />> $ / $>


内联



由于性能和代码大小的原因,编译时通常会优先使用枚举成员替换其数值等价物:

  enum Foo {X = 4} 
var y = Foo.X; //发出var y = 4;

规范调用这个替换,我将其称为内联因为听起来更冷。有时您将不会希望枚举成员被内联,例如因为枚举值可能会在将来的API版本中更改。






枚举,它们如何工作?



让我们根据枚举的每个方面打破这一点。不幸的是,这四个部分中的每一个都将参考所有其他部分的内容,所以你可能需要多次阅读这个整体。



计算vs非计算(常数)



枚举成员可以是计算。该规范调用非计算成员常量,但我会称之为非计算,以避免与 混淆



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



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



当且仅当它具有一个初始化器时,声明枚举(即环境枚举)的成员是常量。否则,它被计算。请注意,在声明枚举中,只允许使用数字初始值设置。示例:

 声明枚举Foo {
X,//计算
Y = 2,//非计算
Z,//计算!不是3!小心!
Q = 1 + 1 //错误
}

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



const vs non-const < h2>

const



枚举声明可以有 const 修饰符。如果枚举是 const 所有引用其成员内联。

  const enum Foo {A = 4} 
var x = Foo.A; //发出为var x = 4;,总是

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



non-const



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



declare(ambient)vs non-declare



一个重要的前言是TypeScript中的声明具有非常具体的含义:此对象存在于其他地方。它用于描述现有的对象。使用声明定义不存在的对象可能会产生不良后果;



声明



A 声明枚举不会发出查找对象。如果这些成员被计算(参见上面的计算与非计算),则引用它的成员。



重要的是要注意其他形式的引用 declare枚举 是允许的,例如这个代码不是一个编译错误,但是将在运行时失败:

  //注意:假设没有其他文件实际上在运行时创建了一个Foo var 
declare枚举Foo {Bar}
var s ='Bar';
var b = Foo [s]; //失败

此错误属于不要骗到编译器的类别。如果在运行时没有名为 Foo 的对象,请不要写声明枚举Foo



一个声明const enum const enum 不同,除非是--preserveConstEnums(见下文)。



非声明



非声明枚举产生一个查找对象,如果它不是 const 。内联如上所述。



- preserveConstEnums标志



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






常见错误



最常见的错误是在常规的枚举 const enum 中使用声明枚举 code>会更合适。一个常见的形式是:

  module MyModule {
//声明此枚举与declare存在,但它没有...
export declare枚举Lies {
Foo = 0,
Bar = 1
}
var x = Lies.Foo; //依赖于内联
}

模块SomeOtherCode {
// x在运行时结束为'undefined'
import x = MyModule.Lies;

//尝试使用查找对象,它应该存在
//运行时错误,canot读取未定义的
console.log(x [x.Foo])的属性0 ;
}

记住黄金规则:从不声明不实际存在的东西。如果您想要查找对象,请使用 const enum ,如果您希望内联,或枚举






TypeScript中的更改



在TypeScript 1.4和1.5之间,行为(请参阅 https://github.com/Microsoft/TypeScript/issues/2183 )使非声明非常量枚举的所有成员都被视为已计算,即使它们是使用文字显式初始化的。可以这么说,使内联行为更可预测,更干净地将常规枚举 c / c> c code>。在此更改之前,非常规枚举的非计算成员更加积极地进行了内容。


TypeScript has a bunch of different ways to define an enum:

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? What does const or declare mean on the declarations here?

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

解决方案

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

"lookup object"

If you write this enum:

enum Foo { X, Y }

TypeScript will emit the following object:

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, e.g. when writing Foo.X or Foo['X'], and to serve as a mapping from numbers to strings. 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".

"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.ts file are "ambient" (either requiring an explicit declare modifier or having it implicitly, depending on the declaration type).

"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.


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.

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 (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.

A member of a declare enum (i.e. ambient enum) is constant if and only if it has an initializer. Otherwise, it is computed. Note that in a declare enum, only numeric initializers are allowed. 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. 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 vs non-const

const

An enum declaration can have the const modifier. If an enum is const, all references to its members inlined.

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 Foo in the above code except as part of a member reference. No Foo object will be present at runtime.

non-const

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

declare (ambient) vs non-declare

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

declare

A declare enum will not emit a lookup object. 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, e.g. this code is not a compile error but will fail at runtime:

// 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!

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

non-declare

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

--preserveConstEnums flag

This flag has exactly one effect: non-declare const enums will emit a lookup object. 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. 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. Use const enum if you always want inlining, or enum if you want the lookup object.


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

这篇关于不同的枚举变体如何在TypeScript中工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆