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 }
如果我在运行时尝试使用Gamma
中的值,则会收到错误,因为Gamma
未定义,但Delta
或{的情况并非如此{1}}? Alpha
或const
对声明的含义是什么?
还有一个declare
编译器标志 - 这与这些标志如何相互作用?
答案 0 :(得分:185)
您需要注意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
或1
并希望获得相应的字符串"X"
或"Y"
。
在TypeScript中,您可以"声明"编译器应该知道的事情,但实际上不会发出代码。当你有像jQuery这样的库定义一些你想要类型信息的对象(例如$
)时,这很有用,但是不需要编译器创建的任何代码。规范和其他文档指的是这样做的声明是在" ambient"上下文;值得注意的是,.d.ts
文件中的所有声明都是"环境" (要么需要显式的declare
修饰符,要么隐式使用它,具体取决于声明类型。)
出于性能和代码大小的原因,在编译时将枚举成员的引用替换为其数字等效项通常更可取:
enum Foo { X = 4 }
var y = Foo.X; // emits "var y = 4";
规范称为替换,我将其称为内联,因为它听起来更酷。有时你会不想要枚举枚举成员,例如因为枚举值可能会在未来版本的API中发生变化。
让我们通过枚举的每个方面来打破这个问题。不幸的是,这四个部分中的每一部分都将引用所有其他部分中的术语,因此您可能需要不止一次地阅读这一部分。
枚举成员可以计算。规范调用非计算成员常量,但我会将它们称为非计算,以避免与 const 混淆。
computed 枚举成员是在编译时未知其值的成员。当然,不能内联对计算成员的引用。相反,非计算枚举成员在编译时已知其值 。始终内联对非计算成员的引用。
计算了哪些枚举成员,哪些是非计算的?首先,顾名思义,const
枚举的所有成员都是常量(即非计算的)。对于非常量enum,它取决于您是否正在查看 ambient (声明)枚举或非环境枚举。
当且仅当具有初始化程序时,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和#34中的更改;在底部)
枚举声明可以包含const
修饰符。如果枚举为const
,则所有引用其内联成员。
const enum Foo { A = 4 }
var x = Foo.A; // emitted as "var x = 4;", always
const enums在编译时不会生成查找对象。因此,在上述代码中引用Foo
是错误的,除非作为成员引用的一部分。 1}}对象将在运行时出现。
如果枚举声明没有Foo
修饰符,则仅当成员未计算时才会内联对其成员的引用。非const,非声明枚举将生成查找对象。
一个重要的前言是TypeScript中的const
具有非常特定的含义:此对象存在于其他地方。它用于描述现有的对象。使用declare
来定义实际不存在的对象可能会产生不良后果;我们稍后会探讨这些。
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
与declare const enum
没有区别,但--preserveConstEnums(见下文)除外。
非声明枚举产生查找对象(如果它不是const enum
)。上面描述了内联。
此标志只有一个效果:非声明const枚举将发出查找对象。内联不受影响。这对调试很有用。
最常见的错误是在常规const
或declare enum
更合适时使用enum
。一个常见的形式是:
const 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]);
}
不存在的事情。如果您一直想要内联,请使用declare
;如果您想要查找对象,请使用const enum
。
在TypeScript 1.4和1.5之间,行为发生了变化(参见https://github.com/Microsoft/TypeScript/issues/2183),使非声明非const枚举的所有成员都被视为已计算,即使它们已被明确初始化用文字。这可以说是婴儿"可以说,使内联行为更可预测,更清晰地将enum
的概念与常规const enum
分开。在此更改之前,非常规枚举的非计算成员更加积极地进行内联。
答案 1 :(得分:14)
这里有一些事情发生。让我们逐个讨论。
enum Cheese { Brie, Cheddar }
首先,一个普通的枚举。当编译为JavaScript时,这将发出一个查找表。
查找表如下所示:
var Cheese;
(function (Cheese) {
Cheese[Cheese["Brie"] = 0] = "Brie";
Cheese[Cheese["Cheddar"] = 1] = "Cheddar";
})(Cheese || (Cheese = {}));
然后当你在TypeScript中拥有Cheese.Brie
时,它会在JavaScript中发出Cheese.Brie
,其评估为0. Cheese[0]
会发出Cheese[0]
并实际评估为"Brie"
。
const enum Bread { Rye, Wheat }
实际上没有为此发出任何代码!它的值是内联的。以下内容在JavaScript中发出值0:
Bread.Rye
Bread['Rye']
const enum
S'内联可能对性能原因有用。
但是Bread[0]
呢?这将在运行时出错,您的编译器应该捕获它。没有查找表,编译器也没有内联。
请注意,在上述情况下, - prepareConstEnums标志将导致Bread发出查找表。它的值仍然会被内联。
与declare
的其他用法一样,declare
不会发出任何代码,并希望您在其他位置定义实际代码。这不会发出查找表:
declare enum Wine { Red, Wine }
Wine.Red
在JavaScript中发出Wine.Red
,但不会引用任何Wine查找表,因此除非您已将其定义在其他位置,否则它会出错。< / p>
这不会发出查找表:
declare const enum Fruit { Apple, Pear }
但它确实内联! Fruit.Apple
会发出0.但Fruit[0]
会在运行时再次出错,因为它没有内联,也没有查找表。
我已经在this游乐场写了这篇文章。我建议在那里玩,以了解哪个TypeScript发出哪个JavaScript。