一周的日子(星期一,星期二,...,星期日),一年的季节(冬季,春季,夏季,秋季)和基本方向(北,东,南,西)都是具有有限值集合的例子。
让我们看看在JavaScript中创建枚举的四种好方法(及其优缺点)。
基于对象的枚举
让我们来考虑一件T恤衫的尺寸:Small
,Medium
,和Large
。
const Sizes = {
Small: 'small',
Medium: 'medium',
Large: 'large',
}
const mySize = Sizes.Medium
console.log(mySize === Sizes.Medium // logs true
Sizes
是一个基于JavaScript对象的枚举,它有三个具名常量:Sizes.Small
、Sizes.Medium
以及Sizes.Large
。Sizes也是一个字符串枚举,因为具名常量的值是字符串:
'small'
,'medium'
,以及'large'
。Sizes.Medium的值是
'medium'
。优缺点
普通的对象枚举之所以吸引人,是因为它很简单:只要定义一个带有键和值的对象,枚举就可以了。
const Sizes = { Small: 'small', Medium: 'medium', Large: 'large', } const size1 = Sizes.Medium const size2 = Sizes.Medium = 'foo' // Changed! console.log(size1 === Sizes.Medium // logs false
Sizes.Medium
枚举值被意外地改变。size1,虽然被初始化为
Sizes.Medium
,但不再等同于Sizes.Medium
!让我们仔细看看字符串和
symbol
枚举。以及如何冻结枚举对象以避免意外改变的问题。枚举值类型
const Sizes = { Small: 0, Medium: 1, Large: 2 } const mySize = Sizes.Medium console.log(mySize === Sizes.Medium // logs true
上述例子中,
Sizes
枚举是数值枚举,因为值都是数字:0,1,2。symbol枚举:
const Sizes = { Small: Symbol('small', Medium: Symbol('medium', Large: Symbol('large' } const mySize = Sizes.Medium console.log(mySize === Sizes.Medium // logs true
使用
symbol
的好处是,每个symbol
都是唯一的。这意味着,你总是要通过使用枚举本身来比较枚举:const Sizes = { Small: Symbol('small', Medium: Symbol('medium', Large: Symbol('large' } const mySize = Sizes.Medium console.log(mySize === Sizes.Medium // logs true console.log(mySize === Symbol('medium' // logs false
使用
symbol
枚举的缺点是JSON.stringify(
将symbol
字符串化为null
、undefined
,或者跳过有symbol
作为值的属性:const Sizes = { Small: Symbol('small', Medium: Symbol('medium', Large: Symbol('large' } const str1 = JSON.stringify(Sizes.Small console.log(str1 // logs undefined const str2 = JSON.stringify([Sizes.Small] console.log(str2 // logs '[null]' const str3 = JSON.stringify({ size: Sizes.Small } console.log(str3 // logs '{}'
在下面的例子中,我将使用字符串枚举。但是你可以自由地使用你需要的任何值类型。
symbol更容易进行调试。
基于Object.freeze(枚举
在JavaScript中,
Object.freeze(
工具函数可以冻结一个对象。让我们来冻结Sizes
枚举:const Sizes = Object.freeze({ Small: 'small', Medium: 'medium', Large: 'large', } const mySize = Sizes.Medium console.log(mySize === Sizes.Medium // logs true
const Sizes = Object.freeze({ ... }
创建一个冻结的对象。即使被冻结,你也可以自由地访问枚举值:const mySize = Sizes.Medium
。优缺点
const Sizes = Object.freeze({ Small: 'Small', Medium: 'Medium', Large: 'Large', } const size1 = Sizes.Medium const 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 undefined
Sizes.Med1um
表达式(Med1um
是Medium
的错误拼写版本)结果为未定义,而不是抛出一个关于不存在的枚举常量的错误。基于proxy枚举
一个有趣的,也是我最喜欢的实现,是基于代理的枚举。
枚举代理拦截对枚举对象的读和写操作,并且:
- 当访问一个不存在的枚举值时,会抛出一个错误。
- 当一个枚举对象的属性被改变时抛出一个错误
// enum.js
export 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.Medium
console.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 constant
const size2 = Sizes.Medium = 'foo' // throws Error: changing the enum
Sizes.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' #value constructor(value { this.#value = value } toString( { return this.#value } } const mySize = Sizes.Small console.log(mySize === Sizes.Small // logs true console.log(mySize instanceof Sizes // logs true
Sizes
是一个代表枚举的类。枚举常量是该类的静态字段,例如,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' #value constructor(value { this.#value = value } toString( { return this.#value } } const mySize = Sizes.Small console.log(mySize === new Sizes('small' // logs false
mySize
(即Sizes.Small
)不等于new Sizes('small'
。Sizes.Small和
new Sizes('small'
,即使具有相同的#value
,也是不同的对象实例。优缺点
class Sizes { static Small = new Sizes('small' static Medium = new Sizes('medium' static Large = new Sizes('large' #value constructor(value { this.#value = value } toString( { return this.#value } } const size1 = Sizes.medium // a non-existing enum value can be accessed const size2 = Sizes.Medium = 'foo' // enum value can be overwritten accidentally
但你可以控制新实例的创建,例如,通过计算在构造函数内创建了多少个实例。然后在创建超过3个实例时抛出一个错误。
总结
在JavaScript中,有4种创建枚举的好方法。
const MyEnum = { Option1: 'option1', Option2: 'option2', Option3: 'option3' }
普通的对象枚举适合小型项目或快速演示。
const MyEnum = Object.freeze({ Option1: 'option1', Option2: 'option2', Option3: 'option3' }
冻结的对象枚举适合于中型或大型项目,你要确保枚举不会被意外地改变。
export 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' } } }
import { Enum } from './enum' const MyEnum = Enum({ Option1: 'option1', Option2: 'option2', Option3: 'option3' }
代理枚举适用于中型或大型项目,以更好地保护你的枚举不被覆盖或访问不存在的命名常量。
第四种选择是使用基于类的枚举,其中每个命名的常量都是类的实例,并作为类的静态属性被存储:
class MyEnum { static Option1 = new MyEnum('option1' static Option2 = new MyEnum('option2' static Option3 = new MyEnum('option3' #value constructor(value { this.#value = value } toString( { return this.#value } }
如果你喜欢类的话,基于类的枚举是可行的。然而,基于类的枚举比冻结的或代理的枚举保护得更少。
以上就是本文的全部内容,如果对你有所帮助,欢迎点赞、收藏、转发~