编码中使用的枚举类型, 多半是为了便于代码提示和开发方便, 能快速查看包含的成员. 比如有些第三方库 keymirror, 开发中遇到了一个有趣的库, 他的一些用法可以借鉴, Enumify, 本文部分内容翻译自其官方文档
下面来对枚举模式进行一些探讨.
常见版本
在 JavaScript 中没有 enum 类型, typescript 有, 比如
enum Color {
red,
yellow,
blue
}
当这一段 ts 被 compile 成 js 的时候, 就会是下面这个样子
const Color = {
red: 0,
yellow: 1,
blue: 2
}
这是最常见的枚举形式, 但是有以下的问题
- 对打印不友好, 如果
console.log(Color.red)会只有一个数字 - 类型不安全, 枚举值不唯一, 可以被其他数字干扰. 比如我可以再次设置
Color.blue为1, 就会造成与Color.red的冲突 - 成员无法检查. 没办法检查给出的值是否是
Color的成员
那么很自然而然的想到另外一种方法
const Color = {
red: 'red',
yellow: 'yellow',
blue: 'blue'
}
甚至于为了唯一性
const Color = {
red: Symbol('red'),
yellow: Symbol('yellow'),
blue: Symbol('blue')
}
这里顺便提一下, Symbol 是无法转换成字符串的, 也就是说'' + Symbol('red')或Symbol('red').toString()都是错误的用法.
assert.equal(String(Symbol('red')) === 'Symbole(red)')
那么如何去判断成员呢?
function checkMember(enumObject, value) {
return Object.values(enumObject).includes(value);
}
基于类版本
使用自定义类可以满足更好的成员检测以及枚举值的灵活性
class Color {
static red = new Color('red')
static yellow = new Color('yellow')
static blue = new Color('blue')
static fakeRed = new Color('red')
constructor(name) {
this.name = name
}
toString() {
return `Color.${this.name}`
}
}
这种语法很像 Java 里的枚举类型声明 看看上面列举的三个问题:
- 打印
console.log('Color: ', Color.red) // Color: Color.red
- 枚举值唯一性: 至少都是新对象, 比如
fakeRed, 那也是唯一的. - 成员检测
Color.green instanceof Color // true
Enumify 一个枚举的辅助库
更详细的可参考其 github 的 document, 这里只做其核心功能, 以及自己工作中用到的进行展示
class Color extends Enumify {
static red = new Color()
static yellow = new Color()
static blue = new Color()
}
实例属性
console.log(Color.red.enumKey === 'red') // true, 自动添加属性名到枚举 key
console.log(Color.red.enumOrigin === 0) // true, 表示原始枚举值
原型方法
其实现了.toString()
console.log('Color: ', Color.red) // Color: Color.red
静态属性
enumKeys 所有枚举 key 数组 和 enumValues 所有枚举值数组
Color.enumKeys // ['red', 'yellow', 'blue']
Color.enumValues // [Color.red, Color.yellow, Color.blue]
继承的静态方法: enumValueOf, 根据 key 获取 枚举值
Color.enumValueOf('yellow') // Color.yellow
由上, 可能你已经猜到, 也可以实现迭代
for(let c of Color) {
console.log(c) // Color.red Color.yellow Color.blue
}
实际举例
包含实例属性的枚举值
工作日的例子
class WeedDays extends Enumify {
static monday = new WeekDays(true)
static tuesday = new WeekDays(true)
static wednesday = new WeekDays(true)
static thursday = new WeekDays(true)
static friday = new WeekDays(true)
static sarturday = new WeekDays(true)
static sunday = new WeekDays(true)
constructor(isWorkDay) {
super(isWorkDay) // 注意调用父级构造函数
this.isWorkDay = isWorkDay
}
}
console.log(WeekDays.monday.isWorkDay) // true
根据枚举值进行切换
function nextDay(weekDay) {
switch(weekDay) {
case WeekDays.monday:
return WeekDays.tuesday
// .....
case WeekDays.sunday:
return WeekDays.monday
default:
thorw new Error('Must be WeekDays static property')
}
}
枚举值包含实例 getter
class WeekDays extends Enumify {
static monday = new WeekDays({
get nextDay() {return WeekDays.tuesday}
})
// ...
static sunday = new WeekDays({
get nextDay() {return WeekDays.monday}
})
constructor(props) {
super()
Object.defineProperties(this, Object.getOwnPropertyDescriptors(props))
}
}
console.log(WeekDays.monday.nextDay) // WeekDays.tuesday
通过实例方法实现状态机
class State extends Enumify {
static start = new State({
done:false,
accept(x){
if (x === 1) return State.one
return State.start
}
})
static one = new State({
done: false,
accept(x) {
if (x === 1) return State.two
return State.start
}
})
static two = new State({
done: true
})
constructor(props) {
super()
Object.defineProperties(this, Object.getOwnPropertyDescriptors(props))
}
}
function run(state, inputString) {
for (let ch of inputString) {
if (state.done) break;
state = state.accept(ch)
console.log(`${ch} ==> ${state}`)
}
}
run(State.start, '010111');
// '0 --> State.start'
// '1 --> State.one'
// '0 --> State.start'
// '1 --> State.one'
// '1 --> State.two'
// '1 --> State.three'
日常开发中经常使用 enum 来规避魔数, 让代码更加整洁和规范, 可读性也较高, 希望上文可以帮到你.