您需要了解TypeScript中枚举的四个不同方面。首先,一些定义:
“查找对象”
如果您编写此枚举:
enum 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
or 1
并想要获取相应的字符串"X"
或"Y"
。
“宣布”或“ 环境 ”
在TypeScript中,您可以“声明”编译器应了解的内容,但实际上并不为其发出代码。当您具有类似jQuery的库来定义某些对象(例如,$
您要输入其类型信息但不需要编译器创建的代码的)时,。规范和其他文档将这种声明称为“环境”上下文。重要的是要注意,.d.ts
文件中的所有声明都是“环境”的(declare
根据声明类型需要显式修饰符还是隐式修饰符)。
“内联”
出于性能和代码大小的原因,通常最好在编译时将枚举成员的引用替换为其数值等效项:
enum Foo { X = 4 }
var y = Foo.X; // emits "var y = 4";
规范将其称为替代,我将其称为内联,因为它听起来更酷。有时候你会不希望内联枚举成员,例如,因为在API的未来版本中枚举值可能会更改。
枚举,它们如何工作?
让我们按枚举的每个方面对其进行细分。不幸的是,这四个部分中的每一个都将引用其他所有部分中的术语,因此您可能需要多次阅读整个内容。
计算与非计算(常数)
枚举成员可以计算也可以不计算。该规范要求非计算成员不变,但我会打电话给他们非计算,以避免混乱与常量。
甲计算枚举成员是一个其值在编译时是未知的。当然,不能内联对计算所得成员的引用。相反,非计算的枚举成员是一次其值是在编译时已知的。始终内联对非计算成员的引用。
哪些枚举成员是计算的,哪些不是计算的?首先,const
顾名思义,枚举的所有成员都是常量(即未计算)。对于非常量枚举,取决于您是否正在查看环境(声明)枚举还是非环境枚举。
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中的更改”)
const vs非const
const
枚举声明可以具有const
修饰符。如果枚举为const
,则对所有成员的引用都将内联。
const enum Foo { A = 4 }
var x = Foo.A; // emitted as "var x = 4;", always
const枚举在编译时不会产生查找对象。因此,Foo
在上述代码中进行引用是错误的,只是作为成员引用的一部分。没有Foo
在运行时将不存在对象。
非常量
如果枚举声明不包含const
修饰符,则仅在非计算成员的情况下才内联对其成员的引用。一个非常量,非声明枚举将产生一个查找对象。
声明(环境)与未声明
一个重要的序言是,declare
在TypeScript中具有非常特殊的含义:该对象存在于其他地方。用于描述现有对象。使用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 Foo
!
A declare const enum
与-没什么不同const enum
,除了--preserveConstEnums(请参见下文)。
未声明
如果不是,则未声明的枚举会生成查找对象const
。内联如上所述。
--preserveConstEnums标志
该标志仅具有一种作用:非声明const枚举将发出一个查找对象。内联不受影响。这对于调试很有用。
常见错误
最常见的错误是declare enum
在常规时enum
或const enum
更合适时使用a 。常见的形式是这样的:
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
,如果你总是希望内联,或者enum
如果你想查找的对象。
TypeScript中的更改
在TypeScript 1.4和1.5之间,行为发生了变化(请参阅https://github.com/Microsoft/TypeScript/issues/2183),即使未声明的非const枚举的所有成员都被视为已计算的,即使它们是用文字显式初始化的。可以说,这种“未分裂的婴儿”使内联行为更可预测,并且更清晰地将“ const enum
常规”概念与常规概念分开enum
。在进行此更改之前,会更积极地内联非常量枚举的非计算成员。