# 枚举

使用枚举我们可以定义一些带名字的常量,从而清晰地表达意图或创建一组有区别的用例。

# 枚举类别

  • 数字枚举
enum Direction {
  Up = 1,
  Down,
  Left,
  Right
}

如上,我们定义了一个数字枚举,并设置 Up 使用初始化为 1(缺省为零),其余的成员会从 1 开始自动增长。

枚举的使用也很方便,我们可以通过枚举的属性来访问其中的成员,和枚举的名字来访问枚举类型:

console.log(Direction.Down)
  • 字符串枚举

在字符串枚举中,每个成员都必须使用字符串文本或其他字符串枚举成员进行常量初始化。

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

可见字符串枚举允许你提供一个运行时有意义的并且可读的值,独立于枚举成员的名字。

  • 异构枚举

从技术的角度来说,枚举可以混合字符串和数字成员,也就是异构枚举。

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

除非你真的想要利用 JavaScript 运行时的行为,否则不建议这样做。

# 计算、常量成员

每个枚举成员都带有一个值,它可以是常量或计算出来的。当满足如下条件时,枚举成员被当作是常量:

  • 它是枚举的第一个成员且没有初始化器,这种情况下它被赋予值 0。
  • 它不带有初始化器且它之前的枚举成员是一个数字常量(此时,当前枚举成员的值为它上一个枚举成员的值加 1)。
  • 枚举成员使用常量枚举表达式初始化。

常数枚举表达式是 TypeScript 表达式的子集,它可以在编译阶段求值。当一个表达式满足下面条件之一时,它就是一个常量枚举表达式:

  • 一个枚举表达式字面量(主要是字符串字面量或数字字面量)。
  • 一个对之前定义的常量枚举成员的引用(可以是在不同的枚举类型中定义的)。
  • 带括号的常量枚举表达式。
  • 一元运算符 +, -, ~ 其中之一应用在了常量枚举表达式
  • 常量枚举表达式做为二元运算符 +, -, *, /, %, <<, >>, >>>, &, |, ^ 的操作对象。若常数枚举表达式求值后为 NaN 或 Infinity,则会在编译阶段报错。

除了常量成员,所有其它情况的枚举成员被当作是需要计算得出的值。

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

注意:在计算成员后面的枚举成员都应该被赋值,否则会报错。

# 联合枚举与枚举成员的类型

存在一种特殊的非计算的常量枚举成员的子集:字面量枚举成员。

字面量枚举成员是指不带有初始值的常量枚举成员,或者是值被初始化为:

  • 任何字符串字面量(例如: "foo", "bar", "baz")。
  • 任何数字字面量(例如: 1, 100)。
  • 应用了一元 -符号的数字字面量(例如: -1, -100)。

当所有枚举成员都拥有字面量枚举值时,枚举成员成为了类型:

enum ShapeKind {
  Circle,
  Square,
}

interface Circle {
  kind: ShapeKind.Circle;
  radius: number;
}

同时,枚举类型本身变成了每个枚举成员的联合:

enum E {
  Foo,
  Bar,
}

function f(x: E) {
  if (x !== E.Foo || x !== E.Bar) {
    // Error! This condition will always return 'true' since the types 'E.Foo' and 'E.Bar' have no overlap.ts(2367)
  }
}

# 运行时的枚举

枚举是在运行时真正存在的对象,也就是在转换为 JavaScript 后仍然可以访问到。

# 反向映射

除了创建一个以属性名做为对象成员的对象之外,数字枚举成员还具有了反向映射,从枚举值到枚举名字。

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

TypeScript 可能会将这段代码编译为下面的 JavaScript:

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

生成的代码中,枚举类型被编译成一个对象,它包含了正向映射(name -> value)和反向映射(value -> name)。

注意:TypeScript 不会为字符串枚举成员生成反向映射。

# 常量枚举

正是上面所说枚举是在运行时真正存在的对象,然而有时候我们并不需要这个对象,以避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,此时我们可以使用 const 枚举。

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

常量枚举只能使用常量枚举表达式,并且不同于常规的枚举,它们在编译阶段会被删除。

// 编译前
const enum E {
  X,
  Y,
  Z,
}
let arr: number[] = [E.X, E.Y, E.Z];

//编译后
var arr = [0 /* X */, 1 /* Y */, 2 /* Z */];

注意:常量枚举不允许包含计算成员。

# 外部枚举

外部枚举用来描述已经存在的枚举类型的形状。

declare enum Enum {
  A = 1,
  B,
  C = 2
}

在正常的枚举里,没有初始化方法的成员被当成常数成员;而对于非常数的外部枚举而言,没有初始化方法时被当做需要经过计算的。