说说你对 TypeScript 中枚举类型的理解?应用场景?
问题解析
枚举是 TypeScript 特有的类型(JavaScript 没有),考察候选人对枚举的定义、分类、编译结果以及实际应用场景的理解。需要能够区分数字枚举和字符串枚举的差异。
核心概念
- 枚举(Enum):一组命名的常数集合
- 数字枚举:默认从 0 开始自增的数值
- 字符串枚举:每个成员必须是字符串字面量
- 异构枚举:数字和字符串混合的枚举
- 反向映射:数字枚举支持从值获取键名
- 常量枚举:编译时内联,提高性能
详细解答
1. 是什么
枚举是一个被命名的整型常数的集合,用于声明一组命名的常数。当一个变量有几种可能的取值时,可以将它定义为枚举类型。
通俗来说,枚举就是一个对象的所有可能取值的集合。
在日常生活中也很常见,例如表示星期的 SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY 就可以看成是一个枚举。
枚举的说明形式为:
enum 枚举名 {
标识符① [= 整型常数],
标识符② [= 整型常数],
...
标识符N [= 整型常数],
}
2. 枚举的分类
2.1 数字枚举
当我们声明一个枚举类型时,虽然没有给它们赋值,但它们的值其实是默认的数字类型,而且默认从 0 开始依次累加:
enum Direction {
Up, // 值默认为 0
Down, // 值默认为 1
Left, // 值默认为 2
Right // 值默认为 3
}
console.log(Direction.Up === 0); // true
console.log(Direction.Down === 1); // true
console.log(Direction.Left === 2); // true
console.log(Direction.Right === 3); // true
如果我们将第一个值进行赋值后,后面的值也会根据前一个值进行累加 1:
enum Direction {
Up = 10,
Down, // 11
Left, // 12
Right // 13
}
console.log(Direction.Up, Direction.Down, Direction.Left, Direction.Right);
// 10 11 12 13
2.2 字符串枚举
枚举类型的值也可以是字符串类型:
enum Direction {
Up = 'Up',
Down = 'Down',
Left = 'Left',
Right = 'Right'
}
console.log(Direction['Right'], Direction.Up); // Right Up
注意:如果设定了一个变量为字符串之后,后续的字段也需要赋值字符串,否则报错。
enum Direction {
Up = 'UP',
Down, // error TS1061: Enum member must have initializer
Left, // error
Right // error
}
2.3 异构枚举
即将数字枚举和字符串枚举结合起来混合使用:
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES",
}
console.log(BooleanLikeHeterogeneousEnum.No); // 0
console.log(BooleanLikeHeterogeneousEnum.Yes); // "YES"
通常情况下我们很少会使用异构枚举。
3. 枚举的本质
3.1 编译结果
TypeScript 枚举编译成 JavaScript 后是一个对象:
// TypeScript
enum Direction {
Up,
Down,
Left,
Right
}
// 编译后的 JavaScript
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Down"] = 1] = "Down";
Direction[Direction["Left"] = 2] = "Left";
Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));
3.2 反向映射
上述代码 Direction[Direction["Up"] = 0] = "Up" 可以分成:
Direction["Up"] = 0:正向映射Direction[0] = "Up":反向映射
所以定义枚举类型后,可以通过正反映射拿到对应的值:
enum Direction {
Up,
Down,
Left,
Right
}
console.log(Direction.Up === 0); // true,正向映射
console.log(Direction[0]); // "Up",反向映射
console.log(Direction["Up"]); // 0
注意:字符串枚举没有反向映射。
4. 常量枚举
使用 const enum 定义的枚举为常量枚举,在编译阶段会被删除,成员在使用的地方会被内联进来。
const enum Direction {
Up = 1,
Down,
Left,
Right
}
let directions = [Direction.Up, Direction.Down, Direction.Left, Direction.Right];
编译后:
let directions = [1 /* Up */, 2 /* Down */, 3 /* Left */, 4 /* Right */];
常量枚举可以提升性能,因为不需要生成对象。
5. 枚举合并
多处定义的枚举可以进行合并操作:
enum Direction {
Up = 'Up',
Down = 'Down',
Left = 'Left',
Right = 'Right'
}
enum Direction {
Center = 1
}
console.log(Direction);
// { Up: "Up", Down: "Down", Left: "Left", Right: "Right", Center: 1, 1: "Center" }
深入理解
枚举 vs 对象字面量
// 枚举
enum Status {
Pending = 0,
Success = 1,
Error = 2
}
// 对象字面量(常量对象)
const StatusObj = {
Pending: 0,
Success: 1,
Error: 2
} as const;
type StatusType = typeof StatusObj[keyof typeof StatusObj];
区别:
- 枚举编译后生成双向映射的对象
- 对象字面量更轻量,不会生成额外代码
- 枚举在类型系统中具有唯一性
枚举作为类型
enum Status {
Pending = 'PENDING',
Success = 'SUCCESS',
Error = 'ERROR'
}
function handleStatus(status: Status) {
switch (status) {
case Status.Pending:
console.log('处理中...');
break;
case Status.Success:
console.log('成功!');
break;
case Status.Error:
console.log('失败!');
break;
}
}
handleStatus(Status.Pending); // ok
// handleStatus('PENDING'); // Error: 类型不匹配
应用场景
1. 状态管理
enum OrderStatus {
Pending = 0,
Paid = 1,
Shipped = 2,
Delivered = 3,
Cancelled = 4
}
function getStatusText(status: OrderStatus): string {
const statusMap = {
[OrderStatus.Pending]: '待支付',
[OrderStatus.Paid]: '已支付',
[OrderStatus.Shipped]: '已发货',
[OrderStatus.Delivered]: '已送达',
[OrderStatus.Cancelled]: '已取消'
};
return statusMap[status];
}
2. 权限控制
enum Permission {
None = 0,
Read = 1 << 0, // 1
Write = 1 << 1, // 2
Execute = 1 << 2, // 4
}
// 位运算组合权限
const userPermission = Permission.Read | Permission.Write;
function hasPermission(userPerm: Permission, perm: Permission): boolean {
return (userPerm & perm) === perm;
}
hasPermission(userPermission, Permission.Read); // true
hasPermission(userPermission, Permission.Execute); // false
3. 星期/月份表示
enum Days {
Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
}
enum Months {
January = 1, February, March, April, May, June,
July, August, September, October, November, December
}
console.log(Days["Sunday"] === 0); // true
console.log(Months["January"] === 1); // true
最佳实践
- 优先使用字符串枚举:可读性更好,调试更方便
- 常量枚举提升性能:纯值枚举使用
const enum - 避免异构枚举:除非有特殊需求,否则保持单一类型
- 显式赋值:数字枚举建议显式赋值,避免依赖默认值
- 使用联合类型替代:简单场景可用
type Status = 'pending' | 'success' | 'error'
面试要点
- 枚举定义:一组命名的常数集合
- 数字枚举:默认从 0 自增,支持反向映射
- 字符串枚举:必须显式赋值,不支持反向映射
- 异构枚举:数字和字符串混合,不推荐
- 编译结果:生成双向映射的对象
- 常量枚举:
const enum,编译时内联 - 枚举合并:同名枚举自动合并
- 应用场景:状态码、权限、配置项等有限取值集合