官方定义

使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript支持数字的和基于字符串的枚举。枚举使用 enum

理解:

枚举是列举固定几个值,用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。

直接定义变量的话的话可以随意定义,枚举只能使用你定义好的几个值

例如你定义一个方法接收一个参数,这个参数如果是int型,别人在调用你这个方法时可以传1或2等任意数字,虽然你告诉了同事,只能传0或1,
如果你把参数定义成只能传某个类型,例如是个枚举类,那么别人只能传你枚举类里定义好的几个类型,传其它的就会在编译时期报错。

按照枚举成员分类(数字、字符串、异构)

一、数字枚举

例1

enum Direction {
    Up, // 0
    Down,// 1
    Left,// 2
    Right// 3
}

例2

enum Direction {
    Up = 1,// 1
    Down,// 2
    Left,// 3
    Right// 4
}

例3

enum Direction {
    Up,// 0
    Down=3,// 3
    Left,// 4
    Right// 5
}

例4

enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat};

console.log(Days["Sun"] === 3); // true
console.log(Days["Wed"] === 3); // true
console.log(Days[3] === "Sun"); // false
console.log(Days[3] === "Wed"); // true

以上四个例子我们可以看出

  • 数字枚举如果没有初始化,默认初始化值为0,每项+1
  • 如果有初始化,则在初始化值的基础上,每项+1
  • 如果某项被赋值(可以是负数或负数),那么之后的项在此基础上+1
  • 如果未手动赋值的枚举项与手动赋值的重复了,如例4,TypeScript 是不会察觉到这一点的,但建议尽量避免

反向映射

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

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true

二、字符串枚举

在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。

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

三、异构枚举

将数字枚举与字符串枚举混用,但不建议

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

按照声明方式(普通枚举,const 枚举,外部枚举,外部常量枚举)

一、普通枚举 

上述例子都是常数项

值由计算所得变为计算所得项

如,"blue".length 就是一个计算所得项

enum Color {Red, Green, Blue = "blue".length};

以下情况不被允许

enum Color {Red = "red".length, Green, Blue};

根据官方定义,不带初始化器的枚举要么被放在第一的位置,要么被放在使用了数字常量或其它常量初始化了的枚举后面。

上述代码会报错,因为Red是计算项,而Green紧接其后却无法获取初始值

二、常数枚举 (const enum 定义的枚举)

const enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

会被编译为

var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];

常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员,假如包含了计算成员,则会在编译阶段报错。

三、外部枚举(declare enum 定义的枚举)

种声明枚举的方式比较特别,使用的时候需要非常谨慎,该枚举类型不会生成反向映射

// 声明语 + 关键词 + 枚举名称
declare enum ChineseZodiac {
  rat = 1,
  cattle,
  tiger,
  rabbit,
  dragon
}
console.log(ChineseZodiac)
console.log(ChineseZodiac.rat)

你会发现,无论你是访问枚举本身还是内部成员,均会报错: ChineseZodiac is not defined
编译之后并没有生成该枚举,也就是说,声明的外部枚举是没有被编译的,导致在 runtime 的时候就会报错
这就让人很头疼,既然不能访问,那为何要能做出这个声明呢。

官网对其的描述是:外部枚举用来描述已经存在的枚举类型的形状,这样听起来很晦涩。

通过代码来分析

  • 外部枚举是为了描述当前环境中已经存在的对象的,这个对象可以存在任意的地方,但是一定是已声明的;
    1-1- 一个 .html 后缀文件,为了引入 .ts 文件编译之后的结果,用于调试
  • 1-2- .ts 源文件
  • 1-3- 编译之后的 .js 文件
  • 1-4- 其他 .js 资源文件
  • 1-5- 访问结果
  • 会发现是不会报错的,但是你可能会疑问了,这个不就是访问自己声明的一个变量吗,那跟 .ts 文件中声明的枚举有什么关系?图样图森破,外部枚举类似于 ts 的类型断言,只要在开发中有这个声明,意味着在当前开发环境上下文中一定存在当前这个对象,你可以随意使用当前对象;当然也就意味着你声明外部枚举的时候慎重,我是否真的需要这样做,不然 runtime 使用的时候就出错了;
  • 外部枚举还可以防止声明枚举的命名冲突和成员冲突
    2-1- 我在上面文件结构基础上新增了一个 enum.ts 文件,并在里面声明了一个普通枚举,但是枚举成员和外部枚举成员相同

    2-2- 之所以会有这样的提示,是 declare 的作用,因为 ts 类型系统能够侦测到当前整个文件目录上下文中的所有 declare 声明的变量,编译器也会有语法提示;

四、外部常量枚举(declare 和 const 关键词联合声明)

这个枚举类型和 const 枚举类型并没有什么区别,只是会提示是否有枚举命名冲突和成员冲突,该枚举类型不会生成反向映射

// 声明语 + 修饰符 + 关键词 + 枚举名称
declare const enum ChineseZodiac {
  rat = 1,
  cattle,
  tiger,
  rabbit,
  dragon
}
ChineseZodiac.dragon => 5/* dragon */