0%

【054】typescript:枚举类型(enum)

枚举是 TypeScript 的少数功能之一,它不是 JavaScript 的类型级扩展。枚举允许开发者定义一组命名常量。使用枚举可以更轻松地记录意图,或创建一组不同的案例。TypeScript 提供基于数字和基于字符串的枚举。

typescript 枚举

数字枚举

先看例子

1
2
3
4
5
6
7
8
enum Type01 {
red,
green,
blue
}
console.log(Type01.red) // 0
console.log(Type01.green) // 1
console.log(Type01.blue) // 2

从例子可以看出,枚举中的每一个组员默认是从 0 开始的,如果我们希望从 1 开始,可以这样写

1
2
3
4
5
6
7
8
enum Type02 {
red = 1,
green,
blue
}
console.log(Type02.red) // 1
console.log(Type02.green) // 2
console.log(Type02.blue) // 3

或者这样写

1
2
3
4
5
6
7
8
enum Type03 {
red,
green = 3,
blue
}
console.log(Type03.red) // 0
console.log(Type03.green) // 3
console.log(Type03.blue) // 4

如上,我们定义了 Type02Type03 两个枚举,发现我们不论初始化 red 还是 green,余下的成员都是自动递增的。

字符串枚举

1
2
3
4
5
6
7
8
enum Type04 {
red = 'red',
green = 'green',
blue = 'blue'
}
console.log(Type04.red) // red
console.log(Type04.green) // green
console.log(Type04.blue) // blue

字符串枚举没有自增长的行为,字符串枚举可以很好的序列化,字符串枚举允许你提供一个运行时有意义的并且可读的值,独立于枚举成员的名字。。
那么,如果我们只初始化 green 会怎么样?

1
2
3
4
5
6
7
enum Type05 {
red, // 正常
green = 'green',
blue // 枚举成员必须具有初始化表达式
}
console.log(Type05.red) // 0
console.log(Type05.green) // green

如上,我们可以知道,没有初始化器的枚举要么放在初始化器枚举的前面,要么必须在使用数字常量或其他常量枚举成员初始化的数字枚举之后。

异构枚举

枚举可以混合字符串和数字成员

1
2
3
4
5
6
enum Type06 {
no = 0,
yes = 'yes'
}
console.log(Type06.no) // 0
console.log(Type06.yes) // yes

接口枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum Type07 {
name = '老王',
sex = '男'
}
interface Person {
name: Type07.name,
sex: Type07.sex
}
let person: Person = {
name: Type07.name,
sex: Type07.sex
}
console.log(person.name) // 老王
console.log(person.sex) // 男

计算成员和常量成员

常量成员: 每个枚举成员都有一个与之关联的值 ,可以是常量或计算值。在以下情况下,枚举成员被认为是常量。

1
2
3
4
5
6
7
8
9
// 如下,Type08 中的第一个成员 name 没有初始化,在这种情况下,它被赋值为 0,而第二个成员sex的值是第一个成员name的值加1。
enum Type08 {
name,
sex,
age = 18
}
console.log(Type08.name) // 0
console.log(Type08.sex) // 1
console.log(Type08.age) // 18

const枚举

为了避免在访问枚举值时支付 额外生成的代码额外的间接成本,可以使用 const 枚举。

1
2
3
4
5
6
7
8
9
10
11
const enum Type09 {
red,
green,
blue
}
const color = [
Type09.red,
Type09.green,
Type09.blue
]
console.log(color) // [ 0, 1, 2 ]

const 声明和 普通声明的区别

1
2
3
4
5
6
7
8
// const 声明
const enum Type10 {
name = '老王'
}
console.log(Type10.name)

// 编译后
console.log("\u8001\u738B" /* Type10.name */);
1
2
3
4
5
6
7
8
9
10
// 普通声明
enum Type11 {
name = '老王'
}

// 编译后
var Type11;
(function (Type11) {
Type11["name"] = "\u8001\u738B";
})(Type11 || (Type11 = {}));

从上面的例子可以看出:

  1. const 声明的枚举会被编译成常量
  2. 普通声明的枚举编译完后是个对象

反向映射

除了为成员创建具有属性名称的对象外,数字枚举成员还获得从枚举值到枚举名称的反向映射。它包含了正向映射( name -> value)和反向映射( value -> name)

1
2
3
4
5
6
7
8
9
10
11
12
13
enum Type12 {
name
}
const n = Type12.name
console.log(Type12[n]) // name

// 编译后
var Type12;
(function (Type12) {
Type12[Type12["name"] = 0] = "name";
})(Type12 || (Type12 = {}));
var n = Type12.name;
console.log(Type12[n]); // name

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

1
2
3
4
5
6
7
// 元素隐式具有 "any" 类型,因为类型为 "Type13.name" 的表达式不能用于索引类型 "typeof Type13"。
类型“typeof Type13”上不存在属性“[Type13.name]”。
enum Type13 {
name = '张三'
}
const na = Type13.name
console.log(Type13[na]) // 报错

运行时的枚举

枚举是运行时存在的真实对象。

1
2
3
4
5
6
7
8
9
enum Type14 {
A,
B,
C
}
function fn(obj: { A: number }) {
console.log(obj.A) // 0
}
fn(Type14)

枚举成员的特性

成员只读,无法修改

1
2
3
4
5
enum Type15 {
A,
B
}
Type15.A = 1 // 无法为“A”赋值,因为它是只读属性

可以作为单独的类型存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
enum Type16 {
A = 1,
B = 2
}
let C: Type16 = 3 // 已声明“C”,但从未读取其值,不能将类型“3”分配给类型“Type16”

// 解决方法
enum Type16 {
A = 1,
B = 2
}
let C: Type16 = 2

// 或者
enum Type16 {
A = 1,
B = 2,
C = 3
}
let C: Type16 = 3

数字枚举 value 的类型

1
2
3
4
5
6
7
8
9
enum Type17 {
A, // 默认值
B = 2, // 常量
C = 3 + 4, // 常量表达式
D = 'abc'.length, // 非常量表达式
E = Math.random(), // 随机数
F, // 枚举成员必须具有初始化表达式
G // 枚举成员必须具有初始化表达式
}

如上,在使用多种类型时,编辑器无法判断最后两个枚举成员 FG 应该使用哪种类型的值,所以需要为它们设置初始值。

1
2
3
4
5
6
7
8
9
enum Type17 {
A, // 默认值
B = 2, // 常量
C = 3 + 4, // 常量表达式
D = 'abc'.length, // 非常量表达式
E = Math.random(), // 随机数
F = 0,
G
}

使用枚举的好处

提高代码的可读性

枚举通过为一组相关的数值提供有意义的名字,使得代码更易于理解和阅读。比如每个星期的7天。

1
2
3
4
5
6
7
8
9
enum Week{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}

类型安全

从枚举的特性我们可以知道,枚举和枚举成员可以作为单独的类型存在,这意味着无法将不属于该枚举的值赋值给枚举类型的变量。这在编译时提供了错误检查层,避免了不必要的错误,提高了类型安全。

易于维护

如果你的代码依赖于一组特定的数值,而这些数值可能会在项目的生命周期中改变,使用枚举可以使得这些改变更容易管理。当你需要修改或添加新的值时,只需要更新枚举的定义即可,而不需要搜索和替换整个代码库中的硬编码值。如下,只需要直接修改 People 中的 nameage 即可。

1
2
3
4
5
6
enum People {
name = '小红',
age = 18
}
console.log(People.name)
console.log(People.age)

方便调试

在调试过程中,看到一个具有描述性名称的枚举值比看到一个可能没有任何含义的数值更有帮助。比如每个星期的7天。

1
2
3
4
5
6
7
8
9
enum Week{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}

提高性能

枚举值通常在编译时解析,因此在运行时没有额外的成本。