常量枚举
概述
常量枚举(Const Enums)是 TypeScript 提供的一种特殊的枚举类型,使用 const enum 关键字声明。与普通枚举不同,常量枚举在编译时会被完全内联(inline),不会生成任何 JavaScript 代码,从而减少代码体积并提升性能。常量枚举特别适合在性能敏感的场景中使用,但也有一些使用限制需要注意。
常量枚举的主要优势是零运行时开销:所有枚举成员的访问在编译时会被替换为对应的值,不会生成任何 JavaScript 对象。这使得常量枚举成为管理大量常量的理想选择,特别是在需要频繁访问枚举值的场景中。
基本特性
声明方式
使用 const enum 关键字声明常量枚举,语法与普通枚举相同:
// 常量枚举声明
const enum Direction {
Up,
Down,
Left,
Right
}
// 使用常量枚举
const direction: Direction = Direction.Up;
console.log(direction); // 0编译时内联
常量枚举在编译时会被完全内联,不会生成任何 JavaScript 代码:
// TypeScript 代码
const enum Status {
Pending,
Processing,
Completed
}
function getStatus(): Status {
return Status.Pending;
}
console.log(Status.Processing);编译后的 JavaScript 代码(简化版):
// 注意:常量枚举本身不会生成任何代码
function getStatus() {
return 0; // Status.Pending 被内联为 0
}
console.log(1); // Status.Processing 被内联为 1可以看到,常量枚举在编译后完全消失,所有对枚举成员的访问都被替换为对应的值。
数字常量枚举
数字常量枚举与普通数字枚举类似,支持自动递增和自定义值:
// 自动递增
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字符串常量枚举
字符串常量枚举的每个成员都必须显式指定值:
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:不支持反向映射
常量枚举不支持反向映射,即使对于数字常量枚举也是如此:
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:不能作为值使用
常量枚举不能作为值使用,只能通过成员访问:
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:不能使用计算值
常量枚举的成员值必须是编译时常量,不能使用计算值:
// ❌ 错误:不能使用计算值
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 可能无法正确推断常量枚举的类型,需要显式类型注解:
const enum Status {
Pending,
Completed
}
// ⚠️ 可能需要显式类型注解
function getStatus(): Status {
return Status.Pending; // 通常可以推断
}
// 在某些复杂场景中可能需要
const status: Status = Status.Pending;使用示例
示例 1:HTTP 状态码(性能优化)
使用常量枚举定义 HTTP 状态码,在频繁访问的场景中减少运行时开销:
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:配置选项(减少代码体积)
使用常量枚举定义配置选项,减少编译后的代码体积:
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:游戏状态机(性能敏感场景)
在游戏开发等性能敏感的场景中,使用常量枚举管理状态:
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:错误代码系统
使用常量枚举定义错误代码,在错误处理中减少开销:
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 端点,在编译时内联字符串值:
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 enum | enum |
| 编译后代码 | 完全内联,无 JavaScript 代码 | 生成 JavaScript 对象 |
| 代码体积 | ✅ 零开销 | ⚠️ 增加代码体积 |
| 运行时性能 | ✅ 最优(直接使用值) | ⚠️ 需要对象查找 |
| 反向映射 | ❌ 不支持 | ✅ 数字枚举支持 |
| 作为值使用 | ❌ 不支持 | ✅ 支持 |
| 遍历枚举 | ❌ 不支持 | ✅ 支持 |
| 调试体验 | ⚠️ 显示内联值 | ✅ 显示枚举对象 |
| 类型检查 | ✅ 完全支持 | ✅ 完全支持 |
编译后代码对比
普通枚举:
// TypeScript
enum Status {
Pending,
Completed
}
console.log(Status.Pending);// 编译后的 JavaScript
var Status;
(function (Status) {
Status[Status["Pending"] = 0] = "Pending";
Status[Status["Completed"] = 1] = "Completed";
})(Status || (Status = {}));
console.log(Status.Pending); // 需要对象查找常量枚举:
// TypeScript
const enum Status {
Pending,
Completed
}
console.log(Status.Pending);// 编译后的 JavaScript
console.log(0); // 直接内联为值,无对象查找选择建议
使用常量枚举的场景:
性能敏感:游戏循环、高频调用的函数等
typescriptconst enum GameState { Playing, Paused }代码体积敏感:需要减少打包体积的场景
typescriptconst enum HttpStatus { OK = 200, NotFound = 404 }不需要运行时特性:不需要反向映射、遍历等
typescriptconst enum LogLevel { Debug, Info, Error }大量常量:定义大量常量,希望零运行时开销
typescriptconst enum ErrorCode { // 大量错误代码... }
使用普通枚举的场景:
需要反向映射:需要通过值获取名称
typescriptenum Status { Pending, Completed } console.log(Status[0]); // "Pending"需要遍历枚举:需要获取所有枚举值
typescriptenum Color { Red, Green, Blue } Object.values(Color); // 获取所有值需要作为值使用:需要将枚举本身作为值传递
typescriptenum Direction { Up, Down } function getEnum() { return Direction; // 返回枚举对象 }需要更好的调试体验:在调试器中查看枚举对象
常见错误和注意事项
错误 1:尝试使用反向映射
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:尝试作为值使用
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:使用计算值
// ❌ 错误:不能使用计算值
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
}注意事项
注意
- 不支持反向映射:常量枚举不支持通过值获取名称,即使对于数字枚举也是如此
- 不能作为值使用:常量枚举不能作为值传递、遍历或检查
- 编译时内联:所有枚举访问在编译时被替换为值,调试时可能不够直观
- 需要显式类型注解:在某些复杂场景中,可能需要显式类型注解
- 值必须是常量:枚举成员值必须是编译时常量,不能使用运行时计算的值
提示
- 性能优势:在性能敏感的场景中,常量枚举可以显著提升性能
- 代码体积:常量枚举不会增加编译后的代码体积
- 类型安全:常量枚举提供与普通枚举相同的类型安全性
- 适用场景:适合不需要运行时特性的常量定义
- 迁移建议:如果普通枚举不需要反向映射等功能,可以考虑迁移到常量枚举
重要
- 编译时特性:常量枚举是纯编译时特性,在运行时完全不存在
- 调试限制:在调试器中无法查看常量枚举对象,只能看到内联的值
- 外部模块:如果常量枚举需要被外部模块使用,可能需要特殊配置
- 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"字符串常量枚举的类型
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'