TypeScript 枚举陷阱:反向映射、位运算与类型安全详解
TypeScript 的枚举在编译为 JavaScript 后会生成双向映射对象,导致任意数字可通过类型检查。本文深入解析数字枚举的反向映射机制、位运算在组合状态中的应用与潜在风险,以及混合类型枚举的复杂性,帮助开发者避免类型安全漏洞并写出更清晰的代码。
TypeScript 引入了诸多静态编译语言的特性,例如 class(现已成为 JavaScript 的一部分)、interface、generics 以及 union types 等。
然而,今天我们需要重点讨论一个特定的类型,即 enum(枚举)。
在多种静态类型语言中,枚举是一种非常常见的语言特性,例如 C、C#、Java 和 Swift。枚举本质上是代码中可用的一组常量集合。
我们使用 TypeScript 来创建一个代表一周七天的枚举:
enum DayOfWeek {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
};
该枚举使用 enum 关键字声明,后跟 DayOfWeek 名称。接着我们定义枚举中可用的常量。
现在,我们定义一个方法,接收该枚举类型的参数,用于判断传入的参数是否代表周末。
function isItTheWeekend(day: DayOfWeek) {
switch (day) {
case DayOfWeek.Sunday:
case DayOfWeek.Saturday:
return true;
default:
return false;
}
}
最后,我们可以这样调用:
console.log(isItTheWeekend(DayOfWeek.Monday)); // log: false
消除程序中的魔法字符串(Magic Strings)是枚举的一个非常有用的应用场景。
然而,事情远没有表面看起来那么简单。下面的代码调用在 TypeScript 编译后会得到什么结果呢?
console.log(isItTheWeekend(2)); // is this valid?
这个结果可能会让你大吃一惊。这样的调用完全符合 TypeScript 的规则,编译器也会顺利编译通过。
这究竟发生了什么?
上述情况可能让你误以为这是一个 TypeScript 的 Bug。其实,这是 TypeScript 的设计如此。
我们这里新建了一个数字枚举,而且我们可以在 TypeScript Playground 里看看编译出来的结果是什么:
var DayOfWeek;
(function (DayOfWeek) {
DayOfWeek[DayOfWeek["Sunday"] = 0] = "Sunday";
DayOfWeek[DayOfWeek["Monday"] = 1] = "Monday";
DayOfWeek[DayOfWeek["Tuesday"] = 2] = "Tuesday";
DayOfWeek[DayOfWeek["Wednesday"] = 3] = "Wednesday";
DayOfWeek[DayOfWeek["Thursday"] = 4] = "Thursday";
DayOfWeek[DayOfWeek["Friday"] = 5] = "Friday";
DayOfWeek[DayOfWeek["Saturday"] = 6] = "Saturday";
})(DayOfWeek || (DayOfWeek = {}));
运行结果是:

事实上,枚举在运行时就是一个 JavaScript 对象。
这个对象的属性是根据我们定义的枚举常量生成的,同时根据定义顺序生成了对应的数字(例如,Sunday 是 0,Saturday 是 6)。此外,该对象还包含以数字为 key、对应常量字符串为值的属性。
因此,我们可以给上面的方法传入数字,这些数字会映射到对应的枚举值。这意味着枚举既是一个数字常量也是一个字符串常量。
使用场景
如果一个方法接收一个枚举类型参数,但任意数字都能通过编译,这显然破坏了 TypeScript 构建的类型安全体系。那么,这种设计何时适用呢?
假设你有一个服务返回一个 JSON 字符串,你想把这个串建模,对应的某个属性是一个枚举。
在你的数据库里存的是数字。定义一个 TypeScript 枚举可以很容易解决这个问题:
const day: DayOfWeek = 3;
这个在赋值时执行的显式类型转换会把数字转换成枚举的对应常量。也就是说,我们在代码里使用这个枚举会让代码更容易读懂。
控制枚举的数字
枚举的成员对应的数字是根据枚举常量定义的顺序生成的。那我们是否可以控制这个数字的值呢?是可以的。
enum FileState {
Read = 1,
Write = 2
}
只是描述一个文件可能的状态的枚举。
它可能是读也可能是写状态,我们显式地定义了枚举值。现在就很明确什么样的值是合理的,因为显式定义了。
Bit值
但是还有另一个情况很有用,位值(Bit)。
我们再来看一下这个 FileState 枚举,给它添加一个新的枚举值 ReadWrite:
enum FileState {
Read = 1,
Write = 2,
ReadWrite = 3
}
之后假设有一个方法接受这个类型的参数:
const file = await getFile("/path/to/file", FileState.Read | FileState.Write);
我们在 FileState 上使用了 | 操作符。这样我们可以使用位运算来获得一个新的枚举值。在这个例子里面就是 3,ReadWrite 的值。
事实上,我们可以写的更清楚一些:
enum FileState {
Read = 1,
Write = 2,
ReadWrite = Read | Write
}
这个 ReadWrite 的值不是写死的,而是位运算得到的。
但是再这样使用枚举的时候要多加小心。
如下的枚举:
enum Foo {
A = 1,
B = 2,
C = 3,
D = 4,
E = 5
}
如果要得到 E(或者 5),可以位运算得到么:Foo.A | Foo.D or Foo.B | Foo.C?
所以如果要用枚举值做位运算,那么明确如何得到这个值。
控制索引
一般情况下,每个枚举值都会有一个默认的数字值。如果需要也可以明确的给这些枚举值赋值。另外,还可以给某部分枚举赋值:
enum DayOfWeek {
Sunday,
Monday,
Tuesday,
Wednesday = 10,
Thursday,
Friday,
Saturday
}
前几个值是按照位置赋值,Sunday 到 TuesDay 是 0 到 2.之后在 Wednesday 给了一个新值,从这开始每个值都递增 1. 这就可能会出现问题了:
enum DayOfWeek {
Sunday,
Monday,
Tuesday,
Wednesday = 10,
Thursday = 2,
Friday,
Saturday
}
Tuesday 赋值为 2,生成的 JavaScript 是什么样子呢:
var DayOfWeek;
(function (DayOfWeek) {
DayOfWeek[DayOfWeek["Sunday"] = 0] = "Sunday";
DayOfWeek[DayOfWeek["Monday"] = 1] = "Monday";
DayOfWeek[DayOfWeek["Tuesday"] = 2] = "Tuesday";
DayOfWeek[DayOfWeek["Wednesday"] = 10] = "Wednesday";
DayOfWeek[DayOfWeek["Thursday"] = 2] = "Thursday";
DayOfWeek[DayOfWeek["Friday"] = 3] = "Friday";
DayOfWeek[DayOfWeek["Saturday"] = 4] = "Saturday";
})(DayOfWeek || (DayOfWeek = {}));
看起来 Tuesday 和 Thursday 的数值都是 2。
所以,需要显示的设定数值。
非数字枚举
目前为止,我们只讨论了数值枚举,但是枚举的值不一定非的是数字。它也可以是任何常量或者计算值:
enum DayOfWeek {
Sunday = "Sun",
Monday = "Mon",
Tuesday = "Tues",
Wednesday = "Wed",
Thursday = "Thurs",
Friday = "Fri",
Saturday = "Sat"
}
现在就不能给 isItTheWeekend 方法穿数字参数了。这个枚举已经不再是数字枚举。然而,我们也不能传任意字符串进去,因为枚举知道什么样的值才是合理的。
这样也带来另外一个问题:
const day: DayOfWeek = "Mon";
这样是行不通的。
字符串并不能直接给枚举赋值,而是需要一个显示的类型转换:
const day = "Mon" as DayOfWeek; 能不能给它赋其他值呢?事实上枚举可以有很多类型的值:
enum Confusing {
A,
B = 1,
C = 1 << 8,
D = 1 + 2,
E = "Hello World".length
}
这个例子的枚举值都是数字。但是这些数字值可以直接赋值,也可以是计算值,或者是字符串的length属性。如果都是常量的话,那么就可以是多种类型的值:
enum MoreConfusion {
A,
B = 2,
C = "C"
}
这种情况就很难让人理解枚举后面的数据是怎么工作的。所以,最好不要用这样的枚举。
结论
TypeScript 的枚举是对 JavaScript 的一个很好地补充,使用得当将非常有用。它将有助于清理代码中存在的魔术值(magic values)字符串、数字。而且它是类型安全的。
到此这篇关于为什么TypeScript的Enum会出现问题的文章就介绍到这了,更多相关TypeScript Enum内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!
JS语法错误会阻止加载吗?区分SyntaxError与资源加载失败
2026-11-05 16:08:46
JavaScript常见SyntaxError语法错误排查与修复方法
2026-11-05 13:58:44
JS中slice、substring和substr截取字符串的区别
2026-11-05 13:43:47
JS 转 TS 手动迁移最佳实践:从配置 tsconfig 到消除 any 类型
2026-11-05 11:53:32
为什么JS数组push后得到的是数字而不是数组?
2026-11-05 09:59:18








