JavaScript创建枚举
相比直接写数字与字符串值,用枚举表示预定义范围的常量值有很多优点,这里就不做赘述了,但目前为止javascript并没有提供原生的enum类型(typescript当然就支持),通常javascript会借助对象类型来等效实现enum类型,实现方式有多种,这里介绍基于代理的枚举实现方式:
(其它实现方式及优缺点详见: https://zhuanlan.zhihu.com/p/629573201在JavaScript中4种创建枚举方式)
基于代理的枚举
代理枚举是一种有趣的实现方式。一个代理对象是一个特殊的对象,它包装另一个对象并修改对原始对象的操作的行为。代理不会改变原始对象的结构。
枚举代理拦截枚举对象的读取和写入操作,并且:
- 当访问不存在的枚举值时抛出错误
- 当更改枚举对象属性时抛出错误
代理的get()
方法拦截读操作,并在属性名称不存在时抛出错误。set()
方法拦截写操作并抛出错误。它旨在保护枚举对象免受写操作的影响。
//enum.js:
//说明:早期的浏览器或某些IE版本可能不支持Proxy(虽然IE已退出舞台,但考虑一些还需要IE的旧项目场景), 这里尽量做下兼容实现:
(function (global) {
if (!!Proxy) {
global.Enum = function(baseEnum) {
return new Proxy(baseEnum, {
get(target, name) {
if (!baseEnum.hasOwnProperty(name) && !name.toString() === 'Symbol(Symbol.toStringTag)') {
throw new Error(`"${name}" value does not exist in the enum`)
}
return baseEnum[name]
},
set(target, name, value) {
throw new Error('Cannot add a new value to the enum')
}
})
};
} else {
global.Enum = function(baseEnum) {
//return baseEnum;
var newEnum = Object.assign({}, baseEnum);
Object.defineProperties(newEnum
, Object.assign({}
, ...Object.keys(newEnum).map(function (key) {
return {
[key]: {
get: function () {
//if (!newEnum.hasOwnProperty(key) && !key.toString() === 'Symbol(Symbol.toStringTag)') {
// throw new Error(`"${key}" value does not exist in the enum`)
//}
return baseEnum[key] //注意这里不可newEnum[key]否则会死循环
},
set: function(value) {
throw new Error('Cannot update the value of the enum')
}
}
}
})
)
);
//防止新属性被添加到对象中(即防止该对象被扩展),只有当"use strict"时才会报错“Error: Cannot add property XXX, object is not extensible”, 否则只是添加新属性无效但不会报错:
Object.preventExtensions(newEnum);
return newEnum;
};
}
})(window)
使用方式:
import { Enum } from './enum'
const Sizes = Enum({
Small: 'small',
Medium: 'medium',
Large: 'large',
})
const mySize = Sizes.Medium
console.log(mySize === Sizes.Medium) // logs true
代理枚举的使用方式和普通对象枚举完全相同
代理枚举的缺点
代理枚举的缺点是始终需要导入 Enum
工厂函数并将枚举对象包装在其中。与使用其他实现方式相比,代理枚举可能会带来一些性能损失。代理枚举涉及使用 JavaScript 的代理特性,这可能会使枚举对象的访问速度稍慢一丢丢。
参考资料:
在JavaScript中4种创建枚举方式 - 知乎