枚举类型分享 第五节

在 JS 语言里面并不存在语言层面的枚举类型,而 TS 将枚举类型添加到了语言的类型系统里面,这样做的优势:

  1. 开发者更容易清晰的穷尽某个 case 的各种可能;
  2. 更容易以文档的形式列出程序逻辑,增加可读性;

一、整型枚举

//数字型枚举更贴近其他语言中设计的枚举类型
enum Direction {
  Up = 1,
  Down,
  Left,
  Right,
}

上述枚举类型的定义中,我们给 Up 赋值了 1,所有剩下的成员会采用自增长的方式被赋值,比如 Down 就为 2,Left=3, Right=4。
如果不给枚举变量赋值,则数字型枚举采用 0 开头的索引,并自增长赋值。

枚举的使用也很简单,如下

enum UserRespongse {
  No=0,
  Yes=1,
}
function respond(redcipient: string, message: UserResponse): void{
  //...
}
respond("Princess Caroline", UserResponse.Yes);

数字型枚举能与计算类型或者常量类型的枚举变量混用。不过没有初始化器(initializer)的成员要么放在枚举声明的第一位,要么跟在一个被明确赋值的数字枚举变量后面,下面这种情况会报错:

enum E{
  A = getSomeValue(),
  B, //Enum member must have initializer.
}

二、字符串型枚举

字符串类型枚举跟上述数字型枚举差不多,但是在运行时稍微有些不易察觉的区别,下面我们展开说一下。

在字符串型枚举里面各个变量都必须被字符串字面量初始化或者被其他字符串型枚举成员初始化,如下:

enum Direction{
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT",
}

字符串类型的枚举没有默认的自增长机制(这是肯定的),但是也有其优势,就是代码再运行时,当我们查看运行时代码的时候,字符串型枚举每个枚举变量的值都是清晰可阅读的,反观数字型枚举,基本得不到什么有用的信息。

三、异构型枚举

示例如下:

enum BooleanLikeHeterogeneousEnum{
  No = 0,
  Yes = "YES",
}

单纯的从技术实现上来讲,字符串型和整型枚举可以混合使用,但是这并不是一种比较明智的coding 方式,除非你真的想利用 JS 的运行时特点,否则不建议应用这种方式到日常代码开发中。

四、计算型和常量类型枚举变量

每个枚举变量都会被赋值,这个变量值要么是常量,要么是计算后的值。一般下面的情况我们认为枚举成员的值是常量:

  • 枚举成员是在一个位置,并且没有被显示初始化,实际上是默认赋值为 0
// E.X is constant
enum E{
  X,
}
  • 枚举成员没有被显示初始化,并且其前面的枚举成员是整型,这种情况下,当前的枚举成员被赋的值是前面枚举成员值加 1
// All enum members in E1 and E2 are constant
enum E1{
  X,
  Y,
  Z,
}
enum E2{
  A = 1,
  B,
  C,
}
  • 枚举成员被一个常量枚举表达式赋值。一个常量枚举表达式是在编译阶段能被完全计算出值的 TS 表达式的一个子集。如果满足如下条件就可以认定为常量枚举表达式:
  1. 一个字面量枚举表达式(其实就是一个字符串字面量或者数字字面量)
  2. 一个对之前声明的常量枚举成员的引用
  3. 括号括起来的常量枚举表达式
  4. +,-,~一元操作符 表达式
  5. +,-,*,/,%,<<,>>,>>>,&,|,^合法的二元操作表达式
  6. 如果一个常量表达式最后运算结果是NaN或者Infinity,TS 将会报告编译时错误

除此之外其他所有情况都可视为计算型枚举

enum FileAccess {
  // constant members
  None,
  Read = 1 << 1,
  Write = 1 << 2,
  ReadWrite = Read | Write,
  // computed member
  G = "123".length,
}

五、联合枚举和枚举成员类型

常量枚举成员有一个不需要计算的特殊的子集:字面量枚举成员。一个字面量枚举成员是一个不需要初始化值的常量枚举成员或者值是被以下情况初始化的:

  • 任意的字符串字面量(e.g."foo","bar,"baz"
  • 任意的数字字面量(e.g.1,100)
  • 任意数字字面量的一元操作表达式(e.g.-1,-100)

当一个枚举类型里面所有的成员都被赋值了字面量值,就会出现一些特殊的编程模式。

首先:枚举成员也能变成类型。举个栗子,我们可以看到如下某个成员变量仅仅拥有一个枚举变量类型:

enum ShapeKind {
  Circle,
  Square,
}
interface Circle {
  kind: ShapeKind.Circle;
  radius: number;
}
interface Square {
  kind: ShapeKind.Square;
  sideLength: number;
}
let c: Circle = {
  kind: ShapeKind.Square,
//Type 'ShapeKind.Square' is not assignable to type'ShapeKind.Circle'.
  radius: 100,
};

其次,枚举类本身能变成每个枚举成员的联合类型。利用枚举成员组成的联合类型,类型检测系统能清楚的知道枚举本身内部存在的每个成员,TS 从而可以发现 bug,当我们对比错误的变量的时候。示例如下:

enum E {
  Foo,
  Bar,
}
function f(x: E) {
  if (x !== E.Foo || x !== E.Bar) {
//上述 if 判断总是会返回 true,因为 E.Foo 和 E.Bar并没有交集
// 首先检查 x 是否不等于 E.Foo,如果检查通过,根据逻辑运算的短路原则最终表达式判
// 定为 true,如果检查不通过,那么 x 的类型只能为 E.Foo,那么 x !== E.Bar通过,
// 仍然返回 true
  }
}

六、 运行时(runtime)的枚举

枚举是存在在运行时(runtime)中的真实对象,例如:

enum E {
  X,
  Y,
  Z,
}
//上面的枚举类型能作为参数传递进函数
function f(obj: { X: number }) {
  return obj.X;
}
// Works, since 'E' has a property named 'X' which is a number.
f(E);

七、编译时(compile-time)的枚举

尽管枚举在 js 运行时里面是真实存在的对象,但是用 keyof 关键字处理枚举得到的结果可能与标准的对象有所区别。事实上,用 keyof typeof 来获取枚举类型的时候,发现所有的枚举的键都是 string 类型。

enum LogLevel {
  ERROR,
  WARN,
  INFO,
  DEBUG,
}
/**
 * This is equivalent to:
 * type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
 */
type LogLevelStrings = keyof typeof LogLevel;
function printImportant(key: LogLevelStrings, message: string) {
  const num = LogLevel[key];
  if (num <= LogLevel.WARN) {
    console.log("Log level key is:", key);
    console.log("Log level value is:", num);
    console.log("Log level message is:", message);
  }
}
printImportant("ERROR", "This is a message");

八、反向映射

整型枚举也可以反向映射,从枚举成员的值映射为枚举成员的变量名,举个栗子:

enum Enum {
  A,
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"

TS 将上述逻辑转译为如下 js 逻辑:

"use strict";
var Enum;
(function (Enum) {
    Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
let a = Enum.A;
let nameOfA = Enum[a]; // "A"

注意:字符串型枚举没有反向映射

九、常量枚举类型

大多数情况下,枚举类型是十分有效的解决方案。但是在个别情况下,为了避免转译代码带来的额外的开销以及访问枚举类型带来的间接性,于是就出现了 const 关键字声明的枚举类型,如下:

const enum Enum {
  A = 1,
  B = A * 2,
}

常量枚举类型只能使用常量枚举表达式,同时也不想普通的枚举类型,常量枚举类型不会存在于运行时,会在编译时被替换。常量枚举类型的成员会在编译时直接替换为其枚举成员的值。

const enum Direction {
  Up,
  Down,
  Left,
  Right,
}
let directions = [
  Direction.Up,
  Direction.Down,
  Direction.Left,
  Direction.Right,
];
// 上述枚举类型转译后代码如下
"use strict";
let directions = [
    0 /* Up */,
    1 /* Down */,
    2 /* Left */,
    3 /* Right */,
];
posted @ 2020-11-27 10:05  yxl87  阅读(211)  评论(0编辑  收藏  举报