枚举不仅仅是对JS的类型扩展,而且是TS中少有的新特性
数值枚举 vs. 字符串枚举
enum Gender {
Female,
Male
}
枚举成员后面没有任何值的时候,这个枚举就是一个数值枚举,top默认会被初始化为0,后面的成员的值依次自增。
当然也可以显示的给枚举成员初始化值
enum Gender {
Female = 1,
Male
}
同样的,后面的成员值会依次递增。
字符串枚举中的每一个成员都要有一个初始值,这个初始值可以是一个字面量,也可以是别的字符串枚举里成员的值。
enum Gender {
Female = 'female',
Male
}
// ts 编译器会提示 Male 必须有个初始值。
字符串枚举并不存在像数值枚举那样的自增行为。字符串枚举的好处是,成员值可以通过字符串携带信息,不像数值枚举一样是难以记忆的数字(实际上反向映射可以做到拿到成员的名字,不过麻烦一点),让程序可读性更高。
异构枚举
如果一个枚举的成员值既有数值也有字符串,那么它就是一个异构枚举。
enum Gender {
Female = 'female',
Male = 0,
others
}
// others 是1
enum Gender {
others
Female = 'female',
Male = 1
}
// others 是 0
enum Gender {
Female = 'female',
others
Male = 0
}
// 编译器提示others得有初始值
Lookup Object
当用enum关键字定义常规的枚举值的时候(注意enum关键字前面既没有const,也没有declare)
enum Gender {
Female,
Male
}
会被ts compiler编译成
"use strict";
var Gender;
(function (Gender) {
Gender[Gender["Female"] = 0] = "Female";
Gender[Gender["Male"] = 1] = "Male";
})(Gender || (Gender = {}));
可以看到Gender编译时实际上被转换成一个有4个键值对的对象,我们把它称作Lookup Object。这个对象既可以按照枚举值中的成员名字查找对应的值,比如Gender.Female会得到0;也可以反向映射(Reverse Mapping),用成员的值查出对应的成员名字,比如Gender[0]会得到"female"。
注:只有数值枚举成员会的转换会有反向映射,字符串枚举成员并不会。
常量枚举成员和计算枚举成员
计算枚举成员的值在编译阶段是不可知的,通常在运行的时候才可以知道,而常量枚举成员的值编译阶段是已经确定的。
常量枚举成员的情形如果枚举的第一个成员没有初始值,默认会被赋值0
枚举成员没有初始值,但是它前面的一个成员值是数值常量
常量枚举表达式(就是编译时完全可以计算结果的表达式)。
常量枚举表达式的情形数值或字符串自变量
引用定义好的常量枚举成员
带括号的常量枚举表达式
带一元操作符的常量枚举表达式(+,-,~)
带二元操作符的常量枚举表达式(+,-,*,/,%,<>,>>>,&,|,^)
除上面列举情形下的是常量枚举成员,其他的都是计算枚举成员。
const和non-const枚举
如果我们在代码中大量使用普通的枚举,最终生成的代码无疑会变多,因为枚举会转换成真实的对象代码。比如下面的转换过程
enum Gender {
Female,
Male
}
console.log(Gender.Female)
//-------- tsc生成的代码 -----
"use strict";
// let a = [1, 2,3]var Gender;
(function (Gender) {
Gender[Gender["Female"] = 0] = "Female";
// others, Gender[Gender["Male"] = 1] = "Male";
})(Gender || (Gender = {}));
console.log(Gender.Female);
我们只是想打印个Female的值0而已,tsc令人窒息的生成了一堆代码。有没有办法不要上面生成lookup object的一坨代码,直接编译成console.log(0)呢。
我们知道ts代码是静态编译的,我们如果想只生成console.log(0),那么Gender.Female的值在编译时应该是确定的才行。回顾之前的内容,会知道常量枚举成员在编译阶段的值是已知的,我们可以利用这一点达到目的。
好在TypeScript可以用const来实现!它的方式就是利用常量枚举成员的特性,在编译阶段做值替换,减少最终生成代码的体积。TS官方文档把这种替换叫做内联(inline)。
在TS的playground里看一下上面的代码使用const enum是什么样的
"use strict";
console.log(0 /* Female */);
十分完美的实现了我们想要的效果,没有对象生成,仅仅替换了值。
我们根据上面的描述,可以推断出const enum的一些特点首先const enum的成员必须是常量枚举成员(编译时值不确定无法做内联[即替换])
const enum没有办法做反向映射(因为没有对象生成嘛)
没有办法使用定义的枚举”对象“ (比如console.log(Gender), 根本就不存在这个对象值,因为没有对象生成),只能使用枚举成员。
通过内联的方式,生成的代码比起常规的枚举要简洁许多,优化了代码。
declare enum 和 declare const enum
declare的作用是告诉ts编译器一些外部的上下文(ambient context),具体的可以查看一下官方文档。类似const enum,它在编译时是没有js代码生成的。一个常见的例子就是jQuery中的$符号,它是一个全局的变量,但是编译器是不知道的,必须告诉编译器存在一个这样的全局变量,我可以在项目的任何地方都使用。对于declare enum,也是同样的意思,编译器通过这个声明会知道,有个提前定义好的Gender可以在全局环境下使用。
declare enum Gender {
Female,
Male
}
console.log(Gender.Female)
//-------- tsc生成的代码 -----
"use strict";
console.log(Gender.Female);
编译时并不会报错,需要注意的是,如果并不存在一个全局可用的Gender,console.log这一句在执行的时候会报错。
为什么呢?道理很简单,因为你欺骗了编译器,告诉它存在一个实际不存在的全局变量。
然而有些时候,我们又想在全局,像之前的const enum中提到的一样,开启inline的功能,这时候就要用到declare const enum。
declare const enum Gender {
Female,
Male
}
console.log(Gender.Female)
//-------- tsc生成的代码 -----
"use strict";
console.log(0 /* Female */);
它不会介意你全局有没有一个实际的Gender,而是仅仅在编译做了值的替换。再回忆下,什么样的枚举成员编译时才能inline?当然是常量枚举成员。
枚举作为类型
枚举成员作为类型
当枚举成员值为字面量时,枚举成员本身就可以作为类型来使用。TSC检测到类型不匹配
枚举作为类型
枚举本身作为类型的时候, 有点像联合类型(union type), 即TSC知道foo是来自Gender枚举成员的候选集。
Enums · TypeScriptwww.typescriptlang.orghttps://stackoverflow.com/questions/40227401/const-enum-in-typescriptstackoverflow.comhttps://stackoverflow.com/questions/28818849/how-do-the-different-enum-variants-work-in-typescriptstackoverflow.com