- 引用
- 链接 👉 js 枚举提案 github proposal-enum
- 术语
- 枚举(Enum)
- 数字枚举(Numeric Enum)
- 字符串枚举(String Enum)
- 常数项(constant member)
- 计算所得项(computed member)
- 外部枚举(Ambient Enums)
- 枚举并不是 js 中原生的概念,在其他语言中它都是老朋友了(Java、C#、Swift 等)。目前也已经存在给 js(ECMAScript)引入枚举支持的 proposal-enum 提案,但还未被提交给 TC39 ,仍处于 Stage 0 阶段。
- 枚举类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等
- 枚举使用
enum
关键字来定义 - 枚举允许为一组数值设置友好的名字
- 枚举类型在编译结果中并不会被擦除,它表现为一个 js 对象
- 常量枚举默认会被擦除
- 【数字枚举】枚举成员默认会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射
- 【数字枚举】有反向映射,这意味着你可以使用枚举名称访问枚举值,也可以使用枚举值访问枚举名称
- 【数字枚举】你也可以给枚举项手动赋值,未手动赋值的枚举项会接着上一个枚举项递增 1
- 在手动赋值时,需要注意递增值与所赋值重复的情况,ts 允许这种重复的行为发生,并不会报错。但要注意,这将会导致枚举值被覆盖的问题。
enum Days {
Sun,
Mon,
Tue,
Wed,
Thu,
Fri,
Sat,
}
// 上述枚举变量 Days 的编译结果:
// var Days;
// (function (Days) {
// Days[(Days["Sun"] = 0)] = "Sun";
// Days[(Days["Mon"] = 1)] = "Mon";
// Days[(Days["Tue"] = 2)] = "Tue";
// Days[(Days["Wed"] = 3)] = "Wed";
// Days[(Days["Thu"] = 4)] = "Thu";
// Days[(Days["Fri"] = 5)] = "Fri";
// Days[(Days["Sat"] = 6)] = "Sat";
// })(Days || (Days = {}));
Days.Sun // 0
Days.Mon // 1
Days.Tue // 2
Days.Wed // 3
Days.Thu // 4
Days.Fri // 5
Days.Sat // 6
enum Days {
Sun = 7,
Mon = 1, // 后续会依次递增
Tue,
Wed,
Thu,
Fri,
Sat,
}
Days.Sun // 7
Days.Mon // 1
Days.Tue // 2
Days.Wed // 3
Days.Thu // 4
Days.Fri // 5
Days.Sat // 6
enum Days {
Sun = 3,
Mon = 1,
Tue,
Wed,
Thu,
Fri,
Sat,
}
// 编译结果:
// var Days;
// (function (Days) {
// Days[(Days["Sun"] = 3)] = "Sun";
// Days[(Days["Mon"] = 1)] = "Mon";
// Days[(Days["Tue"] = 2)] = "Tue";
// Days[(Days["Wed"] = 3)] = "Wed";
// Days[(Days["Thu"] = 4)] = "Thu";
// Days[(Days["Fri"] = 5)] = "Fri";
// Days[(Days["Sat"] = 6)] = "Sat";
// })(Days || (Days = {}));
Days["Sun"] === 3 // true
Days["Wed"] === 3 // true
Days[3] === "Sun" // false
Days[3] === "Wed" // true
- 【数字枚举】手动赋值的枚举项也可以为小数或负数,此时后续未手动赋值的项的递增步长仍为 1
enum Days {
Sun = -1,
Mon,
Tue,
Wed,
Thu = -5.5,
Fri,
Sat,
}
Days.Sun // -1
Days.Mon // 0
Days.Tue // 1
Days.Wed // 2
Days.Thu // -5.5
Days.Fri // -4.5
Days.Sat // -3.5
- 【字符串枚举】每个成员都必须使用字符串字面量进行初始化
- 【字符串枚举】无反向映射,这意味着你可以使用枚举名称访问枚举值,但无法使用枚举值访问枚举名称
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
// 可以通过键访问值
let dirVal: string = Direction.Up
// 无法通过值访问键,得到的将是 undefined
let dirKey: Direction = Direction["DOWN"]
console.log(dirVal) // UP
console.log(dirKey) // undefined
- 数字枚举和字符串枚举可以在一个枚举变量中共用,但是最好不要混用。
// bad practice
enum Direction {
Up = "UP",
Down = "DOWN",
Left = 1,
Right,
}
Direction.Up // UP
Direction.Down // DOWN
Direction.Left // 1
Direction.Right // 2
- 由于枚举在最终的编译结果中呈现为一个对象类型,所以那些 js 中针对对象类型的 api,也可以用于枚举变量。
enum Color {
Red = "RED",
Blue = "BLUE",
Green = "GREEN",
}
Object.entries(Color)
// [ [ 'Red', 'RED' ], [ 'Blue', 'BLUE' ], [ 'Green', 'GREEN' ] ]
enum NumericEnum {
A,
B,
C,
}
Object.entries(NumericEnum)
// [
// [ '0', 'A' ],
// [ '1', 'B' ],
// [ '2', 'C' ],
// [ 'A', 0 ],
// [ 'B', 1 ],
// [ 'C', 2 ]
// ]
- 【数字枚举】你可以使用延迟求值的枚举值
- 如果你使用了延迟求值,那么没有使用延迟求值的枚举成员必须放在使用常量枚举值声明的成员之后,或者放在第一位。否则该成员将无法正确被初始化,ts 会报错。
const returnNum = () => 100 + 499;
enum Items {
Foo = returnNum(),
Bar = 599,
Baz
}
Object.entries(Items)
// [
// [ '599', 'Bar' ],
// [ '600', 'Baz' ],
// [ 'Foo', 599 ],
// [ 'Bar', 599 ],
// [ 'Baz', 600 ]
// ]
const returnNum = () => 100 + 499;
enum Items {
Foo = returnNum(),
// error 枚举成员必须具有初始化表达式。
Baz,
Bar = 599,
}
const returnNum = () => 100 + 499;
enum Items {
Baz,
Foo = returnNum(),
Bar = 599,
}
Object.entries(Items)
// [
// [ '0', 'Baz' ],
// [ '599', 'Bar' ],
// [ 'Baz', 0 ],
// [ 'Foo', 599 ],
// [ 'Bar', 599 ]
// ]
- 枚举项分类
- 常数项
- 计算所得项
- 如果紧接在计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错。
// ok
enum Color {
Red,
Green,
Blue = "blue".length,
}
// error 枚举成员必须具有初始化表达式。
enum Color {
Red = "red".length,
Green,
Blue,
}
不难发现,其实当你使用“延迟求值的枚举值”时,其实就是在使用“计算所得项”。
枚举成员被当作是常数的条件:
【常量枚举】常量枚举也称常数枚举
- 【常量枚举】常数枚举是使用
const enum
定义的枚举类型 - 【常量枚举】常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员
const enum Directions {
Up,
Down,
Left,
Right,
}
let directions = [
Directions.Up,
Directions.Down,
Directions.Left,
Directions.Right,
];
// 编译结果
// var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
- 【常量枚举】常量枚举的表现、编译产物还受到配置项
--isolatedModules
以及--preserveConstEnums
等的影响 - 【外部枚举】外部枚举是使用
declare enum
定义的枚举类型 - 【外部枚举】外部枚举也可以与
const
共用
declare enum Directions {
Up,
Down,
Left,
Right,
}
let directions = [
Directions.Up,
Directions.Down,
Directions.Left,
Directions.Right,
];
declare const enum Directions {
Up,
Down,
Left,
Right,
}
let directions = [
Directions.Up,
Directions.Down,
Directions.Left,
Directions.Right,
];
- 【枚举 vs. 对象】从枚举类型的编译结果来看,不难发现其实就是 js 的对象类型。很多使用枚举的场景,我们其实都可以使用传统的对象这种数据结构来解决。何时使用枚举?何时使用对象?对于这俩问题,实在不好作答,不过可以从语义上对它们做一个简单的区分
- 【枚举 vs. 对象】何时使用枚举?
- 表示一组相关的常量值:枚举用于定义一个你知道的固定集合,而不是一个可以动态更改的集合。
- 使用枚举可以使代码读者明白这是一组特定的、有限的值。
- 与普通的 js 对象不同,你不能在定义后添加、修改或删除枚举的成员。
- 需要用到数字枚举的逆映射功能。
- 【枚举 vs. 对象】何时使用对象?
- 动态值集合:如果你的键值对集合不是固定的,或者在运行时可能会更改,使用对象更为合适。
- 对象可以存储方法和计算属性,而枚举则主要用于存储值。
- 当你需要更复杂的结构,如嵌套对象或不同类型的值时,使用对象会更加合适。
enum Color {
Red,
Blue,
Green,
}
// ok(推荐)
let c1: Color = Color.Red;
// ok(不推荐)
let c2: Color = 0;
// error 不能将类型“4”分配给类型“Color”。
let c3: Color = 4;