JavaScript实现枚举的几种方法总结

 更新时间:2023年08月30日 08:23:40   作者:liangyue  
在前端开发中,我们可能经常需要用到枚举,使用枚举的好处是为了让代码的可读性更强,避免直接使用数字或未知的字符串,但是在JavaScript中,要自己实现一个枚举功能,那么大家能想到多少种实现枚举的方法呢,我将介绍几种实现枚举的好方法

基于普通对象

我们来考虑一个场景:T恤的尺寸:Small、Medium、Large三种类型,那么我们使用普通对象的方式来实现,代码如下:

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

基于这种方式实现一个枚举是非常简单的,而且也足够的清晰,此时Sizes就是一个基于JavaScript普通对象的枚举,它有3个命名常量:Size.SmallSize.MediumSize.Large,我们可以通过Size.small来获取对应的枚举值。

优点

  • 简单:使用这种方式实现枚举是非常简单,只需要定义一个带有键和值的对象即可

缺点

  • 容易被外部修改

当我们在维护一个大型的代码仓库时,枚举值可能会被意外更改,代码如下:

const size1 = Sizes.Medium
const size2 = Sizes.Medium = 'foo' // Changed!
console.log(size1 === Sizes.Medium) // logs false

如代码中所示,当枚举被修改后,之后的逻辑将会出现问题,这也就是说,我们实现枚举的时候需要考虑对象的值不能被修改

基于Symbol

我们也可以这样实现一个枚举:

const Sizes = {
  Small: Symbol('small'),
  Medium: Symbol('medium'),
  Large: Symbol('large'),
}
const mySize = Sizes.Large;
console.log(mySize === Sizes.Large); // true
console.log(mySize === Symbol('large')); // false

使用这种方式实现的枚举看上去和机遇普通对象的方式类似,只是把对象中的值修改成了Symbol类型,那么这样和刚才的方式又有什么不同呢?

优点

  • 必须使用枚举本身来进行比较:也就是上面代码中我们必须使用Sizes.Large来比较,再重新创建一个Symbol('large')对比的话则不相等,而对比第一种方法,只要字符串相同即为相同,这种对比方式更加严格了

缺点

  • 不能使用JSON.stringify,使用JSON.stringify会将Symbol转为undefined、null或直接跳过,代码如下:
console.log(JSON.stringify(Sizes.Small)); // undefined
console.log(JSON.stringify([Sizes.Small])); // [null]
console.log(JSON.stringify({ size: Sizes.Small })) // {}
  • 容易被外部修改,这点与第一种方案的情况类似,接下来,我们将介绍如何能够保证枚举值不会被修改的方案

基于Object.freeze

使用Object.freeze可以使一个对象被冻结:被冻结的对象不能再被更改:不能添加新的属性,不能移除现有的属性,不能更改它们的可枚举性、可配置性、可写性或值,对象的原型也不能被重新指定。我们使用这种实现枚举,代码如下:

const Sizes = Object.freeze({
  Small: 'small',
  Medium: 'medium',
  Large: 'large',
})
const mySize = Sizes.Large;
Sizes.Large = '111'
console.log(mySize === Sizes.Large) // true

使用这种方式,就算去修改枚举值也是无效的,如果在严格模式下,这种赋值的情况还会抛出错误。

优点

  • 有效防止枚举值被修改

缺点

  • 当拼写错误时,会直接返回undefined ,比如我们直接获取Sizes.a,此时会返回undefined,这个问题在前面的几个方案中也是同样的,我们在开发过程中,应该是更希望抛出一个错误,这样在开发阶段更直接的发现问题所在。于是,就有了下面一种方案。

基于Proxy

使用Proxy用于创建一个对象的代理,从而实现基本操作的拦截和自定义,Proxy并不会改变原始对象的结构,而且我们可以实现如下两个需求:

  • 访问不存在枚举时,抛出错误
  • 修改枚举对象属性时,抛出错误

这样,就可以同时满足我们前面几种方案遇到的问题了,接下来,我们封装一个函数,代码如下:

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')
    }
  })
}

这个函数中,我们传入一个初始枚举对象,当我们访问某个属性的时候,如果没有将会抛出错误"${name}" value does not exist in the enum,当我们修改值的时候,也会抛出一个错误Cannot add a new value to the enum接下来,我们使用这个函数包装一下Sizes,代码如下:

const Sizes = Enum({
  Small: 'small',
  Medium: 'medium',
  Large: 'large',
})
const mySize = Sizes.Large; // large
Sizes.Small = '1' // 抛出错误: Cannot add a new value to the enum
console.log(Sizes.a) // 抛出错误:"a" value does not exist in the enum

优点

  • 枚举值防止修改
  • 访问不存在的枚举时会抛出错误

缺点

  • 相对复杂,必须导入Enum函数

基于Class

另一个方法是基于JavaScript中的Class类实现的,这个类中包含一组静态的字段,而每一个对应的值本身又是这个实例,代码如下:

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;
  }
}

每一个枚举值都是一个Sizes实例,内部有个私有属性#value, 用来表示枚举的原始值。我们举几个例子来看下使用这种方式实现的具有哪些特性:

const mySize = Sizes.Large;
console.log('mySize', Sizes.Large); // Sizes {}
console.log(mySize === Sizes.Large); // true
console.log(mySize === new Sizes('large')) // false
console.log('mySize string', Sizes.toString()) // large
console.log(mySize instanceof Sizes) // true

优点

  • 可以通过instanceof来判断是否是枚举:上面例子中我们可以判断出来mySize是一个枚举
  • 这种方式枚举的对比是基于实例的:上面例子中mySize === new Sizes('large') ,即使是相同的#value,也是不同的实例

缺点

  • 枚举值可能会被意外修改
  • 访问不存在的枚举时不会抛出错误

总结

上面我们介绍了几种在JavaScript中实现枚举的方式,每种方式都有各自的优缺点,相比之下,我认为:

  • Proxy方式更为灵活,可以按照自己的需求进行更多的定制化;
  • 如果枚举值用的较多,且项目较大,选择Object.freeze方式,防止枚举值被意外修改;
  • 如果您遇到的情况相对简单,使用基于普通对象的方式;

总之,我们最终的实现要尽量的简单,不要过度设计,按照具体情况选择合适的方式。

相关文章

  • JavaScript中实现单体模式分享

    JavaScript中实现单体模式分享

    这篇文章主要介绍了JavaScript中实现单体模式分享,单体模式的定义:单体是一个用来划分命名空间并将一批相关方法和属性组织在一起的对象,如果它能够被实例化,那么只能被实例化一次,需要的朋友可以参考下
    2015-01-01
  • 自动生成文章摘要[JavaScript 版本]

    自动生成文章摘要[JavaScript 版本]

    这篇文章主要介绍了自动生成文章摘要[JavaScript 版本]
    2006-12-12
  • 基于JavaScript实现购物网站商品放大镜效果

    基于JavaScript实现购物网站商品放大镜效果

    大家在日常生活中都有网购的经验,有的网站会有商品放大镜功能,效果非常棒,那么基于js代码是如何实现的呢?下面小编给大家带来了基于js实现购物网站商品放大镜效果,非常不错,感兴趣的朋友参考下吧
    2016-09-09
  • JS判断移动端访问设备并加载对应CSS样式

    JS判断移动端访问设备并加载对应CSS样式

    JS判断不同web访问环境,主要针对移动设备,提供相对应的解析方案,本例是加载不同的css样式
    2014-06-06
  • 浅谈Javascript Base64 加密解密

    浅谈Javascript Base64 加密解密

    这篇文章主要简单介绍了Javascript Base64 加密解密的使用方法,有需要的小伙伴参考下
    2014-12-12
  • 微信小程序实现单个卡片左滑显示按钮并防止上下滑动干扰功能

    微信小程序实现单个卡片左滑显示按钮并防止上下滑动干扰功能

    这篇文章主要介绍了微信小程序实现单个卡片左滑显示按钮并防止上下滑动干扰功能,利用小程序事件处理的api,分别读取触摸开始,触摸移动时,触摸结束的X/Y坐标,根据差值来改变整个卡片的位置,具体实例代码跟随小编一起看看吧
    2019-12-12
  • js跨域问题之跨域iframe自适应大小实现代码

    js跨域问题之跨域iframe自适应大小实现代码

    前几天做公司和开心网合作项目的时候 碰到iframe 跨域自适应的问题刚开始很迷惑 开心网那边技术工程师给我发了一段这样子的代码。
    2010-07-07
  • 微信小程序之间的参数传递、获取的操作方法

    微信小程序之间的参数传递、获取的操作方法

    这篇文章主要介绍了微信小程序中如何获取和传递参数,包括获取当前页面参数、单独input文本框参数的获取、表单获取参数信息、点击表格单元格信息获取行ID以及前端页面跳转传递多个参数等,感兴趣的朋友跟随小编一起看看吧
    2025-01-01
  • JavaScript工厂模式详解

    JavaScript工厂模式详解

    这篇文章主要介绍了JavaScript设计模式之工厂模式,结合完整实例形式分析了工厂模式的概念、原理及javascript定义与使用工厂模式的相关操作技巧,需要的朋友可以参考下
    2021-10-10
  • Jquery中删除元素的实现代码

    Jquery中删除元素的实现代码

    empty用来删除指定元素的子元素,remove用来删除元素,或者设定细化条件执行删除
    2011-12-12

最新评论