返回首页

说说你对 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

最佳实践

  1. 优先使用字符串枚举:可读性更好,调试更方便
  2. 常量枚举提升性能:纯值枚举使用 const enum
  3. 避免异构枚举:除非有特殊需求,否则保持单一类型
  4. 显式赋值:数字枚举建议显式赋值,避免依赖默认值
  5. 使用联合类型替代:简单场景可用 type Status = 'pending' | 'success' | 'error'

面试要点

  1. 枚举定义:一组命名的常数集合
  2. 数字枚举:默认从 0 自增,支持反向映射
  3. 字符串枚举:必须显式赋值,不支持反向映射
  4. 异构枚举:数字和字符串混合,不推荐
  5. 编译结果:生成双向映射的对象
  6. 常量枚举const enum,编译时内联
  7. 枚举合并:同名枚举自动合并
  8. 应用场景:状态码、权限、配置项等有限取值集合