5.4.5 常量枚举成员与计算枚举成员
每个枚举成员都有一个值,根据枚举成员值的定义可以将枚举成员划分为以下两类:
▪常量枚举成员
▪计算枚举成员
5.4.5.1 常量枚举成员
若枚举类型的第一个枚举成员没有定义初始值,那么该枚举成员是常量枚举成员并且初始值为0。示例如下:
01 enum Foo { 02 A, // 0 03 }
此例中,枚举成员A是常量枚举成员,并且“Foo.A”的值为0。
若枚举成员没有定义初始值并且与之紧邻的前一个枚举成员值是数值型常量,那么该枚举成员是常量枚举成员并且初始值为紧邻的前一个枚举成员值加1。如果紧邻的前一个枚举成员的值不是数值型常量,那么将产生错误。示例如下:
01 enum Foo { 02 A, // 0 03 B, // 1 04 } 05 06 enum Bar { 07 C = 'C', 08 D, // 编译错误 09 }
此例中,枚举成员“Foo.A”和“Foo.B”都是常量枚举成员。枚举成员“Bar.D”的定义将产生编译错误,因为它没有指定初始值并且前一个枚举成员“Bar.C”的值不是数值。
若枚举成员的初始值是常量枚举表达式,那么该枚举成员是常量枚举成员。常量枚举表达式是TypeScript表达式的子集,它能够在编译阶段被求值。常量枚举表达式的具体规则如下:
▪常量枚举表达式可以是数字字面量、字符串字面量和不包含替换值的模板字面量。
▪常量枚举表达式可以是对前面定义的常量枚举成员的引用。
▪常量枚举表达式可以是用分组运算符包围起来的常量枚举表达式。
▪常量枚举表达式中可以使用一元运算符“+”“-”“~”,操作数必须为常量枚举表达式。
▪常量枚举表达式中可以使用二元运算符“+”“-”“*”“**”“/”“%”“<<”“>>”“>>>”“&”“|”“^”,两个操作数必须为常量枚举表达式。
例如,下例中的枚举成员均为常量枚举成员:
01 enum Foo { 02 A = 0, // 数字字面量 03 B = 'B', // 字符串字面量 04 C = `C`, // 无替换值的模板字面量 05 D = A, // 引用前面定义的常量枚举成员 06 } 07 08 enum Bar { 09 A = -1, // 一元运算符 10 B = 1 + 2, // 二元运算符 11 C = (4 / 2) * 3, // 分组运算符(小括号) 12 }
字面量枚举成员是常量枚举成员的子集。字面量枚举成员是指满足下列条件之一的枚举成员,具体条件如下:
▪枚举成员没有定义初始值。
▪枚举成员的初始值为数字字面量、字符串字面量和不包含替换值的模板字面量。
▪枚举成员的初始值为对其他字面量枚举成员的引用。
下例中,Foo枚举的所有成员都是字面量枚举成员,同时它们也都是常量枚举成员:
01 enum Foo { 02 A, 03 B = 1, 04 C = -3, 05 D = 'foo', 06 E = `bar`, 07 F = A 08 }
5.4.5.2 计算枚举成员
除常量枚举成员之外的其他枚举成员都属于计算枚举成员。下例中,枚举成员“Foo.A”和“Foo.B”均为计算枚举成员:
01 enum Foo { 02 A = 'A'.length, 03 B = Math.pow(2, 3) 04 }
5.4.5.3 使用示例
枚举表示一组有限元素的集合,并通过枚举成员名来引用集合中的元素。有时候,程序中并不关注枚举成员值。在这种情况下,让TypeScript去自动计算枚举成员值是很方便的。示例如下:
01 enum Direction { 02 Up, 03 Down, 04 Left, 05 Right, 06 } 07 08 function move(direction: Direction) { 09 switch (direction) { 10 case Direction.Up: 11 console.log('Up'); 12 break; 13 case Direction.Down: 14 console.log('Down'); 15 break; 16 case Direction.Left: 17 console.log('Left'); 18 break; 19 case Direction.Right: 20 console.log('Right'); 21 break; 22 } 23 } 24 25 move(Direction.Up); // 'Up' 26 move(Direction.Down); // 'Down'
程序不依赖枚举成员值时,能够降低代码耦合度,使程序易于扩展。例如,我们想给Direction枚举添加一个名为None的枚举成员来表示未知方向。按照惯例,None应作为第一个枚举成员。因此,我们可以将代码修改如下:
01 enum Direction { 02 None, 03 Up, 04 Down, 05 Left, 06 Right, 07 } 08 09 function move(direction: Direction) { 10 switch (direction) { 11 case Direction.None: 12 console.log('None'); 13 break; 14 case Direction.Up: 15 console.log('Up'); 16 break; 17 case Direction.Down: 18 console.log('Down'); 19 break; 20 case Direction.Left: 21 console.log('Left'); 22 break; 23 case Direction.Right: 24 console.log('Right'); 25 break; 26 } 27 } 28 29 move(Direction.Up); // 'Up' 30 move(Direction.Down); // 'Down' 31 move(Direction.None); // 'None'
此例中,枚举成员Up、Down、Left和Right的值已经发生了改变,Up的值由0变为1,以此类推。由于move()函数的行为不直接依赖枚举成员的值,因此本次代码修改对move()函数的已有功能不产生任何影响。但如果程序中依赖了枚举成员的具体值,那么这次代码修改就会破坏现有的代码,如下所示:
01 enum Direction { 02 None, 03 Up, 04 Down, 05 Left, 06 Right, 07 } 08 09 function move(direction: Direction) { 10 switch (direction) { 11 // 不会报错,但是逻辑错误,Direction.Up的值已经不是数字0 12 case 0: 13 console.log('Up'); 14 break; 15 16 // 省略其他代码 17 } 18 }