枚举类型
有时候,程序需要根据不同的取值来执行不同的代码,如果直接使用数值类型变量或字符串类型变量的值来做判断,代码的可读性会很差,让人难以记住其含义,而这些值也散落在代码各处,很难统一管理和维护,示例代码如下。
if(userType==1){
//...
}
else if(userType==2 || userType==3){
//...
}
else if(userType==4){
//...
}
此时就适合使用枚举(enum
)类型来代替数值类型或字符串类型。枚举类型变量通常用于集中定义和管理一组相关的常量,便于在其他地方引用,从而提高代码的可读性和可维护性。TypeScript 中支持数值枚举和字符串枚举。
数值枚举
数值枚举是数值类型的子类型,是默认的枚举类型,其声明示例如下。
enum MonthOfYear {
January,
February,
March,
April,
May,
...//省略后续代码
}
let month: MonthOfYear = MonthOfYear.March;
console.log(month);
在示例代码中,首先定义一个名为 MonthOfYear
的枚举,MonthOfYear
中集中维护与月份相关的各个枚举成员;然后定义一个名为 month
的变量,它的类型为 MonthOfYear
,初始值为 MonthOfYear.March
;最后通过 console.log()
函数将其输出到控制台,输出结果如下。
> 2
在没有显式地给各个枚举成员赋值的情况下,枚举中的第一个成员将从 0 开始取值,而下一个成员会在上一个成员取值的基础上加 1。当不在乎各成员的取值时,使用这种自增长的方式可以让代码显得更精简。但我们也可以显式地给各个成员赋值,例如,修改上述的枚举定义。
enum MonthOfYear {
January = 1,
February = 2,
March,
April,
May,
...//省略后续代码
}
此时执行示例代码,输出 MonthOfYear.March
的值,会发现该枚举成员的取值是在上一个成员取值的基础上加 1,输出结果如下。
> 3
对于分支判断,如果使用枚举,可以很好地解决可读性和可维护性的问题,例如,定义以下枚举。
enum UserType{
Admin,
VIP,
Normal,
Guest
}
当做分支判断时,就可以写为如下代码。
if(userType==UserType.Admin){
//...
}
else if(userType==UserType.VIP || userType==UserType.Normal){
//...
}
else if(userType==UserType.Guest){
//...
}
字符串枚举
字符串枚举的定义方式和数值枚举类似,但区别在于各个成员需要显式地赋值为字符串,示例代码如下。
enum MonthOfYear {
January = "1月",
February = "2月",
March = "3月",
April = "4月",
May = "5月",
...//省略后续代码
}
let month: MonthOfYear = MonthOfYear.March;
console.log(month);
输出结果如下。
> 3月
应慎用的枚举使用方式
虽然善用枚举能够提升代码的可读性和可维护性,但如果误用枚举,则会使得代码的可读性和可维护性都更糟。以下都是 TypeScript 本身支持但应慎用的枚举使用方式。
异构枚举
从技术角度来说,我们可以同时将枚举各成员定义为数值类型和字符串类型,这种混合了两种类型的枚举称为异构枚举,但不推荐这样使用枚举,示例代码如下。
enum MonthOfYear {
January = 1,
February = "2月",
March = 3,
April = "4月",
May = 5,
...//省略后续代码
}
声明合并
TypeScript 支持将枚举成员先拆分后定义,由于 TypeScript 拥有声明合并的特性,因此它们将合并为一个枚举。例如,以下代码定义了一个名为 Answer
的枚举,把枚举成员 yes
和 no
拆开并单独进行定义。虽然最终两个枚举成员 Answer.no
和 Answer.yes
都可以访问,但从可维护性的角度来看,这样的情况应当避免。
enum Answer {
no = 0
}
enum Answer {
yes = 1
}
let a1: Answer = Answer.yes;
let a2: Answer = Answer.no;
索引查找
要访问具体的枚举成员,通常以 “枚举名称.成员名称” 的方式实现。TypeScript 还支持索引形式,即以 “枚举名称[含有成员名称的字符串变量]” 的方式访问具体成员。除非在极其特殊的情况下,否则不推荐使用索引查找。示例代码如下。
enum Answer {
no,
yes
}
let inputString: string = "yes";
let userAnswer: Answer = Answer[inputString];
console.log(userAnswer);
输出结果如下。
> 1
以上代码定义了一个变量 inputString
,其值为 yes
,然后通过索引访问具体枚举成员。从功能上来说这没什么问题,但它会绕过 TypeScript 的编译检查,一旦变量值有误,程序将无法正常运行。例如,如果修改上述代码中 inputString
的定义部分,将其改为以下代码。
...
let inputString: string = "YES";
...
代码看似没有问题,但 yes
变成了 YES
,因此无法检索到对应的成员。在编译 TypeScript 代码时无法检测出该错误。一旦运行代码,输出结果如下。
> undefined
常量枚举
如果使用普通的数值枚举或字符串枚举,在编译成 JavaScript 代码后会产生较多代码来支持各项功能,开销较大且可读性较差,而且很可能被人误用。例如,使用索引查找或反向映射会导致可读性进一步变差,出错率进一步提高。
要解决上述问题,使用常量枚举。要定义常量枚举,只需在普通枚举的定义前面加上 const
关键字,示例代码如下。
const enum Answer {
no,
yes,
}
let actualAnswer: Answer = Answer.yes;
此时如果编译这段代码,你可以发现它与普通枚举编译后产生的 JavaScript 代码存在区别。以下是普通枚举编译后产生的 JavaScript 代码。
var Answer;
(function (Answer) {
Answer[Answer["no"] = 0] = "no";
Answer[Answer["yes"] = 1] = "yes";
})(Answer || (Answer = {}));
var actualAnswer = Answer.yes;
以下是常量枚举编译后产生的 JavaScript 代码,整体上更精简。
var actualAnswer = 1 /* yes */;
如果对常量枚举使用索引查找或反向映射,编译将无法通过,示例代码如下。
let inputString: string = "yes";
//编译错误:只有使用字符串文本才能访问常数枚举成员。ts(2476)
let userAnswer: Answer = Answer[inputString];
//编译错误:只有使用字符串文本才能访问常数枚举成员。ts(2476)
let nameOfyes: string = Answer[Answer.yes];