JavaScript中的四种枚举方式( 二 )


优缺点如果一个枚举属性被意外地改变了,JavaScript会抛出一个错误(在严格模式下):
const Sizes = Object.freeze({Small: 'Small',Medium: 'Medium',Large: 'Large',})const size1 = Sizes.Mediumconst size2 = Sizes.Medium = 'foo' // throws TypeError语句const size2 = Sizes.Medium = 'foo' 对 Sizes.Medium 属性进行了意外的赋值 。
因为Sizes是一个冻结的对象,JavaScript(在严格模式下)会抛出错误:
TypeError: Cannot assign to read only property 'Medium' of object <Object>冻结的对象枚举被保护起来,不会被意外地改变 。
不过,还有一个问题 。如果你不小心把枚举常量拼错了,那么结果将是未undefined:
const Sizes = Object.freeze({Small: 'small',Medium: 'medium',Large: 'large',})console.log(Sizes.Med1um) // logs undefinedSizes.Med1um表达式(Med1um是Medium的错误拼写版本)结果为未定义,而不是抛出一个关于不存在的枚举常量的错误 。
让我们看看基于代理的枚举如何解决这个问题 。
基于proxy枚举一个有趣的,也是我最喜欢的实现,是基于代理的枚举 。
代理是一个特殊的对象,它包裹着一个对象,以修改对原始对象的操作行为 。代理并不改变原始对象的结构 。
枚举代理拦截对枚举对象的读和写操作,并且:

  • 当访问一个不存在的枚举值时,会抛出一个错误 。
  • 当一个枚举对象的属性被改变时抛出一个错误
下面是一个工厂函数的实现,它接受一个普通枚举对象,并返回一个代理对象:
// enum.jsexport function Enum(baseEnum) {return new Proxy(baseEnum, {get(target, name) {if (!baseEnum.hasOwnProperty(name)) {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')}})}代理的get()方法拦截读取操作,如果属性名称不存在,则抛出一个错误 。
set()方法拦截写操作,但只是抛出一个错误 。这是为保护枚举对象不被写入操作而设计的 。
让我们把sizes对象枚举包装成一个代理:
import { Enum } from './enum'const Sizes = Enum({Small: 'small',Medium: 'medium',Large: 'large',})const mySize = Sizes.Mediumconsole.log(mySize === Sizes.Medium) // logs true代理枚举的工作方式与普通对象枚举完全一样 。
优缺点然而,代理枚举受到保护,以防止意外覆盖或访问不存在的枚举常量:
import { Enum } from './enum'const Sizes = Enum({Small: 'small',Medium: 'medium',Large: 'large',})const size1 = Sizes.Med1um// throws Error: non-existing constantconst size2 = Sizes.Medium = 'foo' // throws Error: changing the enumSizes.Med1um抛出一个错误,因为Med1um常量名称在枚举中不存在 。
Sizes.Medium = 'foo' 抛出一个错误,因为枚举属性已被改变 。
代理枚举的缺点是,你总是要导入枚举工厂函数,并将你的枚举对象包裹在其中 。
基于类的枚举另一种有趣的创建枚举的方法是使用一个JavaScript类 。
一个基于类的枚举包含一组静态字段,其中每个静态字段代表一个枚举的常量 。每个枚举常量的值本身就是该类的一个实例 。
让我们用一个Sizes类来实现sizes枚举:
class Sizes {static Small = new Sizes('small')static Medium = new Sizes('medium')static Large = new Sizes('large')#valueconstructor(value) {this.#value = https://www.isolves.com/it/cxkf/yy/js/2023-05-22/value}toString() {return this.#value}}const mySize = Sizes.Smallconsole.log(mySize === Sizes.Small)// logs trueconsole.log(mySize instanceof Sizes) // logs trueSizes是一个代表枚举的类 。枚举常量是该类的静态字段,例如,static Small = new Sizes('small') 。
Sizes类的每个实例也有一个私有字段#value,它代表枚举的原始值 。
基于类的枚举的一个很好的优点是能够在运行时使用instanceof操作来确定值是否是枚举 。例如,mySize instanceof Sizes结果为真,因为mySize是一个枚举值 。
基于类的枚举比较是基于实例的(而不是在普通、冻结或代理枚举的情况下的原始比较):
class Sizes {static Small = new Sizes('small')static Medium = new Sizes('medium')static Large = new Sizes('large')#valueconstructor(value) {this.#value = https://www.isolves.com/it/cxkf/yy/js/2023-05-22/value}toString() {return this.#value}}const mySize = Sizes.Smallconsole.log(mySize === new Sizes('small')) // logs false


推荐阅读