JavaScript中的四种枚举方式
字符串和数字具有无数个值,而其他类型如布尔值则是有限的集合。
一周的日子(星期一,星期二,...,星期日),一年的季节(冬季,春季,夏季,秋季)和基本方向(北,东,南,西)都是具有有限值集合的例子。
当一个变量有一个来自有限的预定义常量的值时,使用枚举是很方便的。枚举使你不必使用魔法数字和字符串(这被认为是一种反模式)。
让我们看看在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中创建枚举的方法?
以上就是本文的全部内容,如果对你有所帮助,欢迎点赞、收藏、转发~
JavaScript中的四种枚举方式的更多相关文章
- 好文:javascript中的四种循环
https://juejin.im/entry/5a1654e951882554b8373622?utm_medium=hao.caibaojian.com&utm_source=hao.ca ...
- java中的四种引用方式(强引用,软引用,弱引用,虚引用)
java内存管理主要有内存分配和内存回收,都不需要程序员负责,垃圾回收的机制主要是看对象是否有引用指向该对象. java中对象的引用主要有四种:强引用,软引用,弱引用,虚引用. Java中提供这四种引 ...
- javascript中对象两种创建方式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Java中的四种引用方式
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与"引用"有关.在Java语言中,将引用又分为强引用.软引用.弱引用 ...
- SQL中的四种连接方式
转自:http://www.cnblogs.com/afirefly/archive/2010/10/08/1845906.html 联接条件可在FROM或WHERE子句中指定,建议在FROM子句中指 ...
- JavaScript中的几种继承方式对比
转自:http://blog.csdn.net/kkkkkxiaofei/article/details/46474069 从’严格’意义上说,JavaScript并不是一门真正的面向对象语言.这种说 ...
- SVG在网页中的四种使用方式
1,直接打开simple.svg <svg xmlns="http://www.w3.org/2000/svg" width="200" height=& ...
- vs2013在使用ef6时,创建模型向导过程中,四种模型方式缺少2种
下载eftool,并安装 https://download.microsoft.com/download/2/C/F/2CF7AFAB-4068-4DAB-88C6-CEFD770FAECD/EFTo ...
- 细数Javascript技术栈中的四种依赖注入
作为面向对象编程中实现控制反转(Inversion of Control,下文称IoC)最常见的技术手段之一,依赖注入(Dependency Injection,下文称DI)可谓在OOP编程中大行其道 ...
- lua中for循环的四种遍历方式
lua中for的四种遍历方式区别 table.maxn 取最大的整数key #table 从1开始的顺序整数最大值,如1,2,3,6 #table == 3 key,value pairs 取每一 ...
随机推荐
- springBoot的全局异常处理
GlobalException.java package com.bank.util; import com.bank.exception.ContentEmpyException; import c ...
- C++生成均匀分布的随机实数
#include<random> #include<iostream> int main() { //定义均匀分布对象,均匀分布区间(a,b)为(2,6) std::unifo ...
- Google Play新功能 让您的应用在Play商店中闪耀
Google Play 商店的商品详情内容是帮助潜在用户了解您应用的功能和价值的最佳方式.您提供的资源和信息 (应用说明.图像和视频) 对用户决定下载应用而言至关重要. 随着越来越多的用户购买移动设备 ...
- 对于Python中RawString的理解(引用)
对于Python中RawString的理解 总结 1.'''作用: 可以表示 "多行注释" ."多行字符串" ."其内的单双引号不转义" 2 ...
- VSCODE C# 运行 找不到任务"BUILD"----C#常用命令
使用 Visual Studio Code 创建 .NET 类库 - .NET | Microsoft Docs 安装vscode.vscode c#相关拓展.MINIGW64 1.创建文件夹 2.用 ...
- Python学习笔记--函数来啦!
函数 函数,就是组织好的,可重复使用的,用来实现特定功能的代码块 实际的小案例:不使用内置函数len,利用函数知识计算出字符串的长度 实现: 函数的基础定义语法 案例:自动查核酸 实现: 函数的传入参 ...
- Spring--依赖注入:setter注入和构造器注入
依赖注入:描述了在容器中建立Bean于Bean之间依赖关系的过程 setter注入 在本来已经在service里面引用了bean的相关方法的基础上,再引用之前已经写过的userDao的对象,即在ser ...
- 文件的上传&预览&下载学习(四)
0.参考博客 https://blog.csdn.net/Chengzi_comm/article/details/53037967 逻辑清晰 https://blog.csdn.net/alli09 ...
- 一招教你 Notion 文章导出到公众号
Notion是一个功能强大的笔记应用程序,有许多优点,包括: 用户友好的界面 跨平台支持 可以结构化组织笔记 多人协作 可以添加多种类型的媒体文件 可以添加评论和任务 这些优点使Notion成为一个广 ...
- 【读书笔记】排列研究-置换角度(分解为Products Of Cycles) 含GroupExploer使用
upd 2020-08-06 23:11完成了最初稿 目录 定义 开胃菜 entrée 群论角度 应用:几何变换 当然要从第一类斯特林数的角度来考虑一下 一个排列的type定义 排旗公式 应用-共轭排 ...