Skip to content

常量枚举

概述

常量枚举(Const Enums)是 TypeScript 提供的一种特殊的枚举类型,使用 const enum 关键字声明。与普通枚举不同,常量枚举在编译时会被完全内联(inline),不会生成任何 JavaScript 代码,从而减少代码体积并提升性能。常量枚举特别适合在性能敏感的场景中使用,但也有一些使用限制需要注意。

常量枚举的主要优势是零运行时开销:所有枚举成员的访问在编译时会被替换为对应的值,不会生成任何 JavaScript 对象。这使得常量枚举成为管理大量常量的理想选择,特别是在需要频繁访问枚举值的场景中。

基本特性

声明方式

使用 const enum 关键字声明常量枚举,语法与普通枚举相同:

typescript
// 常量枚举声明
const enum Direction {
  Up,
  Down,
  Left,
  Right
}

// 使用常量枚举
const direction: Direction = Direction.Up;
console.log(direction);  // 0

编译时内联

常量枚举在编译时会被完全内联,不会生成任何 JavaScript 代码:

typescript
// TypeScript 代码
const enum Status {
  Pending,
  Processing,
  Completed
}

function getStatus(): Status {
  return Status.Pending;
}

console.log(Status.Processing);

编译后的 JavaScript 代码(简化版):

javascript
// 注意:常量枚举本身不会生成任何代码
function getStatus() {
    return 0;  // Status.Pending 被内联为 0
}

console.log(1);  // Status.Processing 被内联为 1

可以看到,常量枚举在编译后完全消失,所有对枚举成员的访问都被替换为对应的值。

数字常量枚举

数字常量枚举与普通数字枚举类似,支持自动递增和自定义值:

typescript
// 自动递增
const enum Priority {
  Low,      // 0
  Medium,   // 1
  High,     // 2
  Critical  // 3
}

// 自定义初始值
const enum HttpStatus {
  OK = 200,
  Created = 201,
  BadRequest = 400,
  NotFound = 404
}

// 部分指定值
const enum Status {
  Pending = 10,    // 10
  Processing,      // 11(自动递增)
  Completed = 100, // 100
  Cancelled        // 101(自动递增)
}

console.log(Priority.High);        // 2
console.log(HttpStatus.OK);         // 200
console.log(Status.Processing);     // 11

字符串常量枚举

字符串常量枚举的每个成员都必须显式指定值:

typescript
const enum HttpMethod {
  Get = "GET",
  Post = "POST",
  Put = "PUT",
  Delete = "DELETE"
}

const enum LogLevel {
  Debug = "debug",
  Info = "info",
  Warn = "warn",
  Error = "error"
}

console.log(HttpMethod.Get);   // "GET"
console.log(LogLevel.Info);    // "info"

使用限制

限制 1:不支持反向映射

常量枚举不支持反向映射,即使对于数字常量枚举也是如此:

typescript
const enum Status {
  Pending,
  Processing,
  Completed
}

// ✅ 正向访问:通过名称获取值
console.log(Status.Pending);    // 0
console.log(Status.Processing); // 1

// ❌ 反向映射:常量枚举不支持
console.log(Status[0]);  // Error: A const enum member can only be accessed using a string literal
console.log(Status[1]);  // Error: A const enum member can only be accessed using a string literal

这是因为常量枚举在编译时被内联,不会生成 JavaScript 对象,因此无法通过值获取名称。

限制 2:不能作为值使用

常量枚举不能作为值使用,只能通过成员访问:

typescript
const enum Direction {
  Up,
  Down
}

// ❌ 错误:不能将常量枚举作为值使用
function getDirection(): typeof Direction {
  return Direction;  // Error: 'Direction' only refers to a type, but is being used as a value here
}

// ❌ 错误:不能遍历常量枚举
Object.keys(Direction);  // Error: 'Direction' only refers to a type, but is being used as a value here
Object.values(Direction);  // Error

// ✅ 正确:只能通过成员访问
const dir: Direction = Direction.Up;
console.log(dir);  // 0

限制 3:不能使用计算值

常量枚举的成员值必须是编译时常量,不能使用计算值:

typescript
// ❌ 错误:不能使用计算值
const enum Status {
  Pending = Math.random(),  // Error: Only numeric literals and other computed enum values are allowed
  Processing = Date.now()   // Error
}

// ✅ 正确:使用字面量值
const enum Status {
  Pending = 0,
  Processing = 1,
  Completed = 2
}

// ✅ 正确:可以引用其他枚举成员
const enum Status {
  Pending = 0,
  Processing = Pending + 1,  // 1
  Completed = Processing + 1 // 2
}

限制 4:需要显式类型注解

在某些情况下,TypeScript 可能无法正确推断常量枚举的类型,需要显式类型注解:

typescript
const enum Status {
  Pending,
  Completed
}

// ⚠️ 可能需要显式类型注解
function getStatus(): Status {
  return Status.Pending;  // 通常可以推断
}

// 在某些复杂场景中可能需要
const status: Status = Status.Pending;

使用示例

示例 1:HTTP 状态码(性能优化)

使用常量枚举定义 HTTP 状态码,在频繁访问的场景中减少运行时开销:

typescript
const enum HttpStatus {
  // 2xx 成功
  OK = 200,
  Created = 201,
  Accepted = 202,
  NoContent = 204,
  
  // 4xx 客户端错误
  BadRequest = 400,
  Unauthorized = 401,
  Forbidden = 403,
  NotFound = 404,
  
  // 5xx 服务器错误
  InternalServerError = 500,
  BadGateway = 502,
  ServiceUnavailable = 503
}

class ApiClient {
  async request(url: string): Promise<Response> {
    const response = await fetch(url);
    const status = response.status;
    
    // 使用常量枚举进行状态判断(编译时内联为数字)
    if (status === HttpStatus.OK) {
      console.log("请求成功");
    } else if (status === HttpStatus.NotFound) {
      console.log("资源未找到");
    } else if (status === HttpStatus.InternalServerError) {
      console.log("服务器错误");
    }
    
    return response;
  }
  
  // 在循环中频繁访问枚举值(性能优势明显)
  checkStatuses(statuses: number[]): void {
    statuses.forEach(status => {
      if (status === HttpStatus.OK) {
        // 在大量迭代中,常量枚举的性能优势明显
        console.log("成功");
      }
    });
  }
}

const client = new ApiClient();
client.request('/api/users');

编译后的代码中,所有 HttpStatus.OK 等访问都被内联为对应的数字(如 200),没有任何运行时开销。

示例 2:配置选项(减少代码体积)

使用常量枚举定义配置选项,减少编译后的代码体积:

typescript
const enum LogLevel {
  Debug = 0,
  Info = 1,
  Warn = 2,
  Error = 3
}

const enum Environment {
  Development = 0,
  Staging = 1,
  Production = 2
}

interface AppConfig {
  logLevel: LogLevel;
  environment: Environment;
}

class Logger {
  private config: AppConfig;
  
  constructor(config: AppConfig) {
    this.config = config;
  }
  
  log(level: LogLevel, message: string): void {
    // 常量枚举值在编译时被内联,无运行时开销
    if (level >= this.config.logLevel) {
      const levelName = this.getLevelName(level);
      console.log(`[${levelName}] ${message}`);
    }
  }
  
  private getLevelName(level: LogLevel): string {
    // 使用 switch 语句,所有枚举值都被内联
    switch (level) {
      case LogLevel.Debug:
        return "DEBUG";
      case LogLevel.Info:
        return "INFO";
      case LogLevel.Warn:
        return "WARN";
      case LogLevel.Error:
        return "ERROR";
      default:
        return "UNKNOWN";
    }
  }
  
  isDevelopment(): boolean {
    // 常量枚举比较在编译时优化
    return this.config.environment === Environment.Development;
  }
}

const logger = new Logger({
  logLevel: LogLevel.Info,
  environment: Environment.Development
});

logger.log(LogLevel.Info, "应用启动");
logger.log(LogLevel.Debug, "调试信息");  // 不会输出(级别低于配置)

示例 3:游戏状态机(性能敏感场景)

在游戏开发等性能敏感的场景中,使用常量枚举管理状态:

typescript
const enum GameState {
  Menu = 0,
  Loading = 1,
  Playing = 2,
  Paused = 3,
  GameOver = 4
}

class Game {
  private state: GameState = GameState.Menu;
  
  // 在游戏循环中频繁调用(性能关键)
  update(): void {
    // 所有枚举比较都被内联为数字比较,性能最优
    switch (this.state) {
      case GameState.Menu:
        this.updateMenu();
        break;
      case GameState.Loading:
        this.updateLoading();
        break;
      case GameState.Playing:
        this.updatePlaying();  // 最频繁的状态
        break;
      case GameState.Paused:
        this.updatePaused();
        break;
      case GameState.GameOver:
        this.updateGameOver();
        break;
    }
  }
  
  setState(newState: GameState): void {
    this.state = newState;
  }
  
  private updateMenu(): void {
    // 菜单逻辑
  }
  
  private updateLoading(): void {
    // 加载逻辑
  }
  
  private updatePlaying(): void {
    // 游戏主循环逻辑(性能关键)
  }
  
  private updatePaused(): void {
    // 暂停逻辑
  }
  
  private updateGameOver(): void {
    // 游戏结束逻辑
  }
}

const game = new Game();
game.setState(GameState.Playing);

// 游戏循环(每帧调用,性能敏感)
function gameLoop() {
  game.update();
  requestAnimationFrame(gameLoop);
}

gameLoop();

在游戏循环这种每帧都要执行的代码中,常量枚举的性能优势非常明显。

示例 4:错误代码系统

使用常量枚举定义错误代码,在错误处理中减少开销:

typescript
const enum ErrorCode {
  // 系统错误
  SystemError = 1000,
  DatabaseError = 1001,
  NetworkError = 1002,
  
  // 验证错误
  ValidationError = 2000,
  InvalidInput = 2001,
  MissingRequired = 2002,
  
  // 权限错误
  Unauthorized = 3000,
  Forbidden = 3001,
  TokenExpired = 3002,
  
  // 业务错误
  NotFound = 4000,
  AlreadyExists = 4001,
  InsufficientFunds = 4002
}

class ErrorHandler {
  // 在错误处理路径中频繁使用(性能重要)
  handleError(code: ErrorCode, message: string): void {
    // 所有枚举比较都被内联
    if (code >= ErrorCode.SystemError && code < 2000) {
      console.error(`[系统错误] ${message}`);
    } else if (code >= ErrorCode.ValidationError && code < 3000) {
      console.error(`[验证错误] ${message}`);
    } else if (code >= ErrorCode.Unauthorized && code < 4000) {
      console.error(`[权限错误] ${message}`);
    } else if (code >= ErrorCode.NotFound && code < 5000) {
      console.error(`[业务错误] ${message}`);
    } else {
      console.error(`[未知错误] ${message}`);
    }
  }
  
  // 使用常量枚举进行错误分类
  getErrorCategory(code: ErrorCode): string {
    if (code >= 1000 && code < 2000) return "系统错误";
    if (code >= 2000 && code < 3000) return "验证错误";
    if (code >= 3000 && code < 4000) return "权限错误";
    if (code >= 4000 && code < 5000) return "业务错误";
    return "未知错误";
  }
}

const handler = new ErrorHandler();
handler.handleError(ErrorCode.NotFound, "资源未找到");
// [业务错误] 资源未找到

handler.handleError(ErrorCode.Unauthorized, "未授权访问");
// [权限错误] 未授权访问

示例 5:字符串常量枚举(API 端点)

使用字符串常量枚举定义 API 端点,在编译时内联字符串值:

typescript
const enum ApiEndpoint {
  Users = "/api/users",
  Posts = "/api/posts",
  Comments = "/api/comments",
  Auth = "/api/auth"
}

const enum HttpMethod {
  Get = "GET",
  Post = "POST",
  Put = "PUT",
  Delete = "DELETE"
}

class ApiClient {
  // 所有端点字符串在编译时内联,无运行时查找
  async getUsers(): Promise<any> {
    return this.request(HttpMethod.Get, ApiEndpoint.Users);
  }
  
  async createPost(data: any): Promise<any> {
    return this.request(HttpMethod.Post, ApiEndpoint.Posts, data);
  }
  
  async deleteComment(id: number): Promise<any> {
    return this.request(
      HttpMethod.Delete,
      `${ApiEndpoint.Comments}/${id}`
    );
  }
  
  private async request(
    method: HttpMethod,
    endpoint: ApiEndpoint | string,
    body?: any
  ): Promise<Response> {
    const options: RequestInit = {
      method,  // 字符串值在编译时内联
      headers: {
        'Content-Type': 'application/json'
      }
    };
    
    if (body) {
      options.body = JSON.stringify(body);
    }
    
    return fetch(endpoint, options);
  }
}

const client = new ApiClient();
client.getUsers();
client.createPost({ title: "Hello" });

常量枚举 vs 普通枚举

对比表格

特性常量枚举普通枚举
声明方式const enumenum
编译后代码完全内联,无 JavaScript 代码生成 JavaScript 对象
代码体积✅ 零开销⚠️ 增加代码体积
运行时性能✅ 最优(直接使用值)⚠️ 需要对象查找
反向映射❌ 不支持✅ 数字枚举支持
作为值使用❌ 不支持✅ 支持
遍历枚举❌ 不支持✅ 支持
调试体验⚠️ 显示内联值✅ 显示枚举对象
类型检查✅ 完全支持✅ 完全支持

编译后代码对比

普通枚举

typescript
// TypeScript
enum Status {
  Pending,
  Completed
}

console.log(Status.Pending);
javascript
// 编译后的 JavaScript
var Status;
(function (Status) {
    Status[Status["Pending"] = 0] = "Pending";
    Status[Status["Completed"] = 1] = "Completed";
})(Status || (Status = {}));

console.log(Status.Pending);  // 需要对象查找

常量枚举

typescript
// TypeScript
const enum Status {
  Pending,
  Completed
}

console.log(Status.Pending);
javascript
// 编译后的 JavaScript
console.log(0);  // 直接内联为值,无对象查找

选择建议

使用常量枚举的场景

  1. 性能敏感:游戏循环、高频调用的函数等

    typescript
    const enum GameState {
      Playing,
      Paused
    }
  2. 代码体积敏感:需要减少打包体积的场景

    typescript
    const enum HttpStatus {
      OK = 200,
      NotFound = 404
    }
  3. 不需要运行时特性:不需要反向映射、遍历等

    typescript
    const enum LogLevel {
      Debug,
      Info,
      Error
    }
  4. 大量常量:定义大量常量,希望零运行时开销

    typescript
    const enum ErrorCode {
      // 大量错误代码...
    }

使用普通枚举的场景

  1. 需要反向映射:需要通过值获取名称

    typescript
    enum Status {
      Pending,
      Completed
    }
    console.log(Status[0]);  // "Pending"
  2. 需要遍历枚举:需要获取所有枚举值

    typescript
    enum Color {
      Red,
      Green,
      Blue
    }
    Object.values(Color);  // 获取所有值
  3. 需要作为值使用:需要将枚举本身作为值传递

    typescript
    enum Direction {
      Up,
      Down
    }
    function getEnum() {
      return Direction;  // 返回枚举对象
    }
  4. 需要更好的调试体验:在调试器中查看枚举对象

常见错误和注意事项

错误 1:尝试使用反向映射

typescript
const enum Status {
  Pending,
  Completed
}

// ❌ 错误:常量枚举不支持反向映射
console.log(Status[0]);  // Error: A const enum member can only be accessed using a string literal

// ✅ 正确:只能正向访问
console.log(Status.Pending);  // 0

错误 2:尝试作为值使用

typescript
const enum Direction {
  Up,
  Down
}

// ❌ 错误:不能将常量枚举作为值使用
function getEnum(): typeof Direction {
  return Direction;  // Error: 'Direction' only refers to a type, but is being used as a value here
}

// ❌ 错误:不能遍历常量枚举
Object.keys(Direction);  // Error

// ✅ 正确:只能通过成员访问
const dir: Direction = Direction.Up;

错误 3:使用计算值

typescript
// ❌ 错误:不能使用计算值
const enum Status {
  Pending = Math.random(),  // Error: Only numeric literals and other computed enum values are allowed
  Completed = Date.now()    // Error
}

// ✅ 正确:使用字面量值或引用其他成员
const enum Status {
  Pending = 0,
  Processing = Pending + 1,  // 可以引用其他成员
  Completed = Processing + 1
}

注意事项

注意

  1. 不支持反向映射:常量枚举不支持通过值获取名称,即使对于数字枚举也是如此
  2. 不能作为值使用:常量枚举不能作为值传递、遍历或检查
  3. 编译时内联:所有枚举访问在编译时被替换为值,调试时可能不够直观
  4. 需要显式类型注解:在某些复杂场景中,可能需要显式类型注解
  5. 值必须是常量:枚举成员值必须是编译时常量,不能使用运行时计算的值

提示

  1. 性能优势:在性能敏感的场景中,常量枚举可以显著提升性能
  2. 代码体积:常量枚举不会增加编译后的代码体积
  3. 类型安全:常量枚举提供与普通枚举相同的类型安全性
  4. 适用场景:适合不需要运行时特性的常量定义
  5. 迁移建议:如果普通枚举不需要反向映射等功能,可以考虑迁移到常量枚举

重要

  1. 编译时特性:常量枚举是纯编译时特性,在运行时完全不存在
  2. 调试限制:在调试器中无法查看常量枚举对象,只能看到内联的值
  3. 外部模块:如果常量枚举需要被外部模块使用,可能需要特殊配置
  4. TypeScript 版本:确保使用的 TypeScript 版本支持常量枚举的所有特性

类型检查示例

类型安全的使用

typescript
const enum Priority {
  Low = 1,
  Medium = 2,
  High = 3
}

// ✅ 正确:使用常量枚举类型
function setPriority(priority: Priority): void {
  console.log(`设置优先级: ${priority}`);
}

setPriority(Priority.High);  // ✅ 正确

// ❌ 错误:类型不匹配
setPriority(99);  // Error: Argument of type '99' is not assignable to parameter of type 'Priority'

// ✅ 使用类型守卫(如果需要处理外部输入)
function isValidPriority(value: number): value is Priority {
  return value === Priority.Low || 
         value === Priority.Medium || 
         value === Priority.High;
}

function setPrioritySafe(priority: number): void {
  if (isValidPriority(priority)) {
    console.log(`设置优先级: ${priority}`);
  } else {
    console.error(`无效的优先级: ${priority}`);
  }
}

setPrioritySafe(Priority.High);  // ✅ "设置优先级: 3"
setPrioritySafe(99);             // ✅ "无效的优先级: 99"

字符串常量枚举的类型

typescript
const enum Theme {
  Light = "light",
  Dark = "dark"
}

// 枚举类型
const theme: Theme = Theme.Light;

// 枚举成员的字面量类型
const lightTheme: Theme.Light = Theme.Light;

// 在函数中使用
function setTheme(theme: Theme): void {
  console.log(`设置主题: ${theme}`);
}

setTheme(Theme.Light);  // "设置主题: light"
setTheme(Theme.Dark);   // "设置主题: dark"

// ❌ 错误:类型不匹配
setTheme("light");  // Error: Argument of type '"light"' is not assignable to parameter of type 'Theme'

相关链接

基于 VitePress 构建