您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > javascript

JavaScript中的四种枚举方式

时间:2023-05-22 15:03:39  来源:  作者: 前端F2E

当一个变量有一个来自有限的预定义常量的值时,使用枚举是很方便的。枚举使你不必使用魔法数字和字符串(这被认为是一种反模式)。

字符串和数字具有无数个值,而其他类型如布尔值则是有限的集合。

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

当一个变量有一个来自有限的预定义常量的值时,使用枚举是很方便的。枚举使你不必使用魔法数字和字符串(这被认为是一种反模式)。

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

基于对象的枚举

枚举是一种数据结构,它定义了一个有限的具名常量集。每个常量都可以通过其名称来访问。

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

JavaScript中创建枚举的一个简单方法(虽然不是最理想的)是使用一个普通的JavaScript对象。

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。

优缺点

如果一个枚举属性被意外地改变了,JavaScript会抛出一个错误(在严格模式下):

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种创建枚举的好方法。

最简单的方法是使用一个普通的JavaScript对象:

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中创建枚举的方法?

本文译自:https://dmitripavlutin.com/javascript-enum/[1]

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

参考资料

[1]

https://dmitripavlutin.com/javascript-enum/:https://dmitripavlutin.com/javascript-enum/



Tags:JavaScript   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
17 个你需要知道的 JavaScript 优化技巧
你可能一直在使用JavaScript搞开发,但很多时候你可能对它提供的最新功能并不感冒,尽管这些功能在无需编写额外代码的情况下就可以解决你的问题。作为前端开发人员,我们必须了解...【详细内容】
2024-04-03  Search: JavaScript  点击:(5)  评论:(0)  加入收藏
你不可不知的 15 个 JavaScript 小贴士
在掌握如何编写JavaScript代码之后,那么就进阶到实践&mdash;&mdash;如何真正地解决问题。我们需要更改JS代码使其更简单、更易于阅读,因为这样的程序更易于团队成员之间紧密协...【详细内容】
2024-03-21  Search: JavaScript  点击:(27)  评论:(0)  加入收藏
构建一个通用灵活的JavaScript插件系统?看完你也会!
在软件开发中,插件系统为应用程序提供了巨大的灵活性和可扩展性。它们允许开发者在不修改核心代码的情况下扩展和定制应用程序的功能。本文将详细介绍如何构建一个灵活的Java...【详细内容】
2024-03-20  Search: JavaScript  点击:(20)  评论:(0)  加入收藏
对JavaScript代码压缩有什么好处?
对JavaScript代码进行压缩主要带来以下好处: 减小文件大小:通过移除代码中的空白符、换行符、注释,以及缩短变量名等方式,可以显著减小JavaScript文件的大小。这有助于减少网页...【详细内容】
2024-03-13  Search: JavaScript  点击:(2)  评论:(0)  加入收藏
跨端轻量JavaScript引擎的实现与探索
一、JavaScript 1.JavaScript语言JavaScript是ECMAScript的实现,由ECMA 39(欧洲计算机制造商协会39号技术委员会)负责制定ECMAScript标准。ECMAScript发展史: 2.JavaScript...【详细内容】
2024-03-12  Search: JavaScript  点击:(2)  评论:(0)  加入收藏
面向AI工程的五大JavaScript工具
令许多人惊讶的是,一向在Web开发领域中大放异彩的JavaScript在开发使用大语言模型(LLM)的应用程序方面同样大有价值。我们在本文中将介绍面向AI工程的五大工具,并为希望将LLM...【详细内容】
2024-02-06  Search: JavaScript  点击:(53)  评论:(0)  加入收藏
18个JavaScript技巧:编写简洁高效的代码
本文翻译自 18 JavaScript Tips : You Should Know for Clean and Efficient Code,作者:Shefali, 略有删改。在这篇文章中,我将分享18个JavaScript技巧,以及一些你应该知道的示例...【详细内容】
2024-01-30  Search: JavaScript  点击:(68)  评论:(0)  加入收藏
使用 JavaScript 清理我的 200GB iCloud,有了一个意外发现!
本文作者在综合成本因素之下,决定用 Java 脚本来清理一下自己的 iCloud,结果却有了一个意外发现,即在 iCloud 中上传同一个视频和删除此视频之后,iCloud 的空间并不一致,这到底是...【详细内容】
2024-01-11  Search: JavaScript  点击:(99)  评论:(0)  加入收藏
JavaScript前端框架2024年展望
Angular、Next.js、React和Solid的维护者和创作者们展望2024年,分享了他们计划中的改进。译自2024 Predictions by JavaScript Frontend Framework Maintainers,作者 Loraine...【详细内容】
2024-01-05  Search: JavaScript  点击:(91)  评论:(0)  加入收藏
JavaScript开发者转向Rust的原因?
JavaScript开发者转向Rust的原因可能有很多,这里列出一些可能的原因: 性能: Rust是一种编译型语言,其性能通常优于JavaScript等解释型语言。对于需要处理大量数据或需要高并发的...【详细内容】
2024-01-04  Search: JavaScript  点击:(99)  评论:(0)  加入收藏
▌简易百科推荐
17 个你需要知道的 JavaScript 优化技巧
你可能一直在使用JavaScript搞开发,但很多时候你可能对它提供的最新功能并不感冒,尽管这些功能在无需编写额外代码的情况下就可以解决你的问题。作为前端开发人员,我们必须了解...【详细内容】
2024-04-03  前端新世界  微信公众号  Tags:JavaScript   点击:(5)  评论:(0)  加入收藏
你不可不知的 15 个 JavaScript 小贴士
在掌握如何编写JavaScript代码之后,那么就进阶到实践&mdash;&mdash;如何真正地解决问题。我们需要更改JS代码使其更简单、更易于阅读,因为这样的程序更易于团队成员之间紧密协...【详细内容】
2024-03-21  前端新世界  微信公众号  Tags:JavaScript   点击:(27)  评论:(0)  加入收藏
又出新JS运行时了!JS运行时大盘点
Node.js是基于Google V8引擎的JavaScript运行时,以非阻塞I/O和事件驱动架构为特色,实现全栈开发。它跨平台且拥有丰富的生态系统,但也面临安全性、TypeScript支持和性能等挑战...【详细内容】
2024-03-21  前端充电宝  微信公众号  Tags:JS   点击:(25)  评论:(0)  加入收藏
构建一个通用灵活的JavaScript插件系统?看完你也会!
在软件开发中,插件系统为应用程序提供了巨大的灵活性和可扩展性。它们允许开发者在不修改核心代码的情况下扩展和定制应用程序的功能。本文将详细介绍如何构建一个灵活的Java...【详细内容】
2024-03-20  前端历险记  微信公众号  Tags:JavaScript   点击:(20)  评论:(0)  加入收藏
对JavaScript代码压缩有什么好处?
对JavaScript代码进行压缩主要带来以下好处: 减小文件大小:通过移除代码中的空白符、换行符、注释,以及缩短变量名等方式,可以显著减小JavaScript文件的大小。这有助于减少网页...【详细内容】
2024-03-13  WangLiwen    Tags:JavaScript   点击:(2)  评论:(0)  加入收藏
跨端轻量JavaScript引擎的实现与探索
一、JavaScript 1.JavaScript语言JavaScript是ECMAScript的实现,由ECMA 39(欧洲计算机制造商协会39号技术委员会)负责制定ECMAScript标准。ECMAScript发展史: 2.JavaScript...【详细内容】
2024-03-12  京东云开发者    Tags:JavaScript   点击:(2)  评论:(0)  加入收藏
面向AI工程的五大JavaScript工具
令许多人惊讶的是,一向在Web开发领域中大放异彩的JavaScript在开发使用大语言模型(LLM)的应用程序方面同样大有价值。我们在本文中将介绍面向AI工程的五大工具,并为希望将LLM...【详细内容】
2024-02-06    51CTO  Tags:JavaScript   点击:(53)  评论:(0)  加入收藏
JS小知识,使用这6个小技巧,避免过多的使用 if 语句
最近在重构我的代码时,我注意到早期的代码使用了太多的 if 语句,达到了我以前从未见过的程度。这就是为什么我认为分享这些可以帮助我们避免使用过多 if 语句的简单技巧很重要...【详细内容】
2024-01-30  前端达人  今日头条  Tags:JS   点击:(56)  评论:(0)  加入收藏
18个JavaScript技巧:编写简洁高效的代码
本文翻译自 18 JavaScript Tips : You Should Know for Clean and Efficient Code,作者:Shefali, 略有删改。在这篇文章中,我将分享18个JavaScript技巧,以及一些你应该知道的示例...【详细内容】
2024-01-30  南城大前端  微信公众号  Tags:JavaScript   点击:(68)  评论:(0)  加入收藏
使用 JavaScript 清理我的 200GB iCloud,有了一个意外发现!
本文作者在综合成本因素之下,决定用 Java 脚本来清理一下自己的 iCloud,结果却有了一个意外发现,即在 iCloud 中上传同一个视频和删除此视频之后,iCloud 的空间并不一致,这到底是...【详细内容】
2024-01-11    CSDN  Tags:JavaScript   点击:(99)  评论:(0)  加入收藏
站内最新
站内热门
站内头条