JavaScript中的四种枚举方式

科技资讯 投稿 23200 0 评论

JavaScript中的四种枚举方式

一周的日子(星期一,星期二,...,星期日),一年的季节(冬季,春季,夏季,秋季)和基本方向(北,东,南,西)都是具有有限值集合的例子。

让我们看看在JavaScript中创建枚举的四种好方法(及其优缺点)。

基于对象的枚举

让我们来考虑一件T恤衫的尺寸:SmallMedium,和Large

const Sizes = {
  Small: 'small',
  Medium: 'medium',
  Large: 'large',
}

const mySize = Sizes.Medium

console.log(mySize === Sizes.Medium // logs true
Sizes是一个基于JavaScript对象的枚举,它有三个具名常量:Sizes.SmallSizes.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字符串化为nullundefined,或者跳过有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表达式(Med1umMedium的错误拼写版本)结果为未定义,而不是抛出一个关于不存在的枚举常量的错误。

基于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
  }
}

如果你喜欢类的话,基于类的枚举是可行的。然而,基于类的枚举比冻结的或代理的枚举保护得更少。

以上就是本文的全部内容,如果对你有所帮助,欢迎点赞、收藏、转发~

编程笔记 » JavaScript中的四种枚举方式

赞同 (120) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽