相比直接写数字与字符串值,用枚举表示预定义范围的常量值有很多优点,这里就不做赘述了,但目前为止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种创建枚举方式 - 知乎