在 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中的四种枚举方式的文章就介绍到这了,更多相关js枚举方式内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
下一篇: 深入了解 Java 枚举及其实现方式
推荐阅读
-
修改 word 文档创建者的方法有哪些?如何修改文档的作者 这两种方法你必须知道 - 软件下载地址:https://www.huibang168.com/static/upload/2Lph6q3Q/file/81/HuibangMetadata_3.7.0.0.exe 除了在 Word 中修改作者信息外,您还可以通过修改文档的元数据来更改作者。元数据存储在文档内部,用于描述文档数据的属性。 具体步骤如下
-
一种结构设计模式,允许在对象中动态添加新行为。它通过创建一个封装器来实现这一目的,即把对象放入一个装饰器类中,然后把这个装饰器类放入另一个装饰器类中,以此类推,形成一个封装器链。这样,我们就可以在不改变原始对象的情况下动态添加新行为或修改原始行为。 在 Java 中,实现装饰器设计模式的步骤如下: 定义一个接口或抽象类作为被装饰对象的基类。 公共接口 Component { void operation; } } 在本例中,我们定义了一个名为 Component 的接口,该接口包含一个名为 operation 的抽象方法,该方法定义了被装饰对象的基本行为。 定义一个实现基类方法的具体装饰对象。 公共类 ConcreteComponent 实现 Component { public class ConcreteComponent implements Component { @Override public void operation { System.out.println("ConcreteComponent is doing something...") ; } } 定义一个抽象装饰器类,该类继承于基类,并将装饰对象作为一个属性。 公共抽象类装饰器实现组件 { protected Component 组件 public Decorator(Component component) { this.component = component; } } @Override public void operation { component.operation; } } } 在这个示例中,我们定义了一个名为 Decorator 的抽象类,它继承了 Component 接口,并将被装饰对象作为一个属性。在操作方法中,我们调用了被装饰对象上的同名方法。 定义一个具体的装饰器类,继承自抽象装饰器类并实现增强逻辑。 公共类 ConcreteDecoratorA extends Decorator { public ConcreteDecoratorA(Component 组件) { super(component); } } public void operation { super.operation System.out.println("ConcreteDecoratorA 正在添加新行为......") ; } } 在本例中,我们定义了一个名为 ConcreteDecoratorA 的具体装饰器类,它继承自装饰器抽象类,并实现了操作方法的增强逻辑。在操作方法中,我们首先调用被装饰对象上的同名方法,然后添加新行为。 使用装饰器增强被装饰对象。 公共类 Main { public static void main(String args) { Component 组件 = new ConcreteComponent; component = new ConcreteDecoratorA(component); 组件操作 } } 在这个示例中,我们首先创建了一个被装饰对象 ConcreteComponent,然后通过 ConcreteDecoratorA 类创建了一个装饰器,并将被装饰对象作为参数传递。最后,调用装饰器的操作方法,实现对被装饰对象的增强。 使用场景 在 Java 中,装饰器模式被广泛使用,尤其是在 I/O 中。Java 中的 I/O 库使用装饰器模式实现了不同数据流之间的转换和增强。 让我们打开文件 a.txt,从中读取数据。InputStream 是一个抽象类,FileInputStream 是专门用于读取文件流的子类。BufferedInputStream 是一个支持缓存的数据读取类,可以提高数据读取的效率,具体代码如下: @Test public void testIO throws Exception { InputStream inputStream = new FileInputStream("C:/bbb/a.txt"); // 实现包装 inputStream = new BufferedInputStream(inputStream); byte bytes = new byte[1024]; int len; while((len = inputStream.read(bytes)) != -1){ System.out.println(new String(bytes, 0, len)); } } } } 其中 BufferedInputStream 对读取数据进行了增强。 这样看来,装饰器设计模式和代理模式似乎有点相似,接下来让我们讨论一下它们之间的区别。 第三,与代理模式的区别: 代理模式的目的是控制对对象的访问,它在对象外部提供一个代理对象来控制对原对象的访问。代理对象和原始对象通常实现相同的接口或继承相同的类,以确保两者可以相互替换。 装饰器模式的目的是动态增强对象的功能,而这是通过对象内部的包装器来实现的。在装饰器模式中,装饰器类和被装饰对象通常实现相同的接口或继承自相同的类,以确保两者可以相互替代。装饰器模式也被称为封装器模式。 在代理模式中,代理类附加了与原类无关的功能。
-
JavaScript - 关于 JavaScript、在 HTML 中嵌入 JS 代码的三种方法、变量
-
在 JavaScript 中枚举的四种方法
-
在 JavaScript 中创建枚举的 4 种方法
-
分享在 JavaScript 中编写枚举的最有效方法
-
在 JavaScript 中创建枚举的四种方法
-
紧急模式问题处理 - 图 1 紧急模式 根本原因分析 应急模式提供了尽可能小的环境,即使无法进入应急模式,也可以在其中修复系统。在应急模式下,系统只安装根文件系统供读取,不尝试安装任何其他本地文件系统,不激活网络接口,只启动一些基本服务。 进入应急模式的原因通常是 /etc/fstab 文件中存在错误,导致文件系统挂载失败。 文件系统中存在错误,导致。 约束和限制 本节适用于 Linux 操作系统紧急模式。程序涉及修复文件系统。修复文件系统有丢失数据的风险,因此请先备份数据,然后再执行修复操作。 处理方法 输入根密码,然后进入修复模式。 在应急模式下,根分区以只读模式挂载。要修改根目录中的文件,需要执行以下命令以读写模式重新挂载根分区。# mount -o rw,remount / 请执行以下命令首先检查 fstab 文件是否有误,然后尝试挂载所有未挂载的文件系统。# mount -a 如果挂载点不存在,请创建一个挂载点。 如果不存在此类设备,请注释或删除挂载行。 如果指定了不正确的挂载选项,请将挂载参数更改为正确的参数。 如果没有发生错误,但出现 UNEXPECTED INCONSISTENCY;RUN fsck MANUALLY 消息(通常是由文件系统错误引起的),请跳至第 7 步。 执行以下命令打开 /etc/fstab 以修改相应的错误。# vi /etc/fstab /etc/fstab 文件包含以下字段,以空格分隔:[文件系统] [dir] [type] [options] [dump] [fsck] 表 1 /etc/fstab 参数 说明 参数 说明 [文件系统] 要挂载的分区或存储设备。 文件系统]列建议以 UUID 的形式写入。执行 blkid 命令可查询设备文件系统 UUID。 参考格式如下: # <device> <dir> <type> <options> <dump> <fsck>; UUID=b411dc99-f0a0-4c87-9e05-184977be8539 /home ext4 defaults 0 2 使用 UUID 的好处是,它们与磁盘顺序无关。如果你在 BIOS 中更改了存储设备的顺序,或重新插入了存储设备,或者因为某些 BIOS 可能会随机更改存储设备的顺序,那么使用 UUID 会更有效率。 [文件系统] 文件系统]的挂载位置。 类型 挂载设备或分区的文件系统类型,支持多种不同的文件系统:ext2、ext3、ext4、reiserfs、xfs、jfs、smbfs、iso9660、vfat、ntfs、swap 和 auto。 设置为自动类型后,挂载命令会猜测所使用的文件系统类型,这对 CDROM 和 DVD 等移动设备非常有用。 选项 挂载时要使用的参数,有些参数是特定文件系统特有的。例如,默认值参数使用文件系统的默认挂载参数,ext4 的默认参数为:rw、suid、dev、exec、auto、nouser、async。 有关更多参数,请执行以下命令查看 man 手册:# man mount
-
Java 类加载器的作用 - 简介:类加载器是 Java™ 中一个非常重要的概念。类加载器负责将 Java 类的字节码加载到 Java 虚拟机中。本文首先详细介绍了 Java 类加载器的基本概念,包括代理模型、加载类的具体过程和线程上下文类加载器等。然后介绍了如何开发自己的类加载器,最后介绍了类加载器在 Web 容器和 OSGi™ 中的应用。 类加载器是 Java 语言的一项创新,也是 Java 语言广受欢迎的重要原因之一。它允许将 Java 类动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 开始出现,最初是为了满足 Java Applets 的需求而开发的,Java Applets 需要从远程位置下载 Java 类文件并在浏览器中执行。现在,类加载器已广泛应用于网络容器和 OSGi。一般来说,Java 应用程序的开发人员不需要直接与类加载器交互;Java 虚拟机的默认行为足以应对大多数情况。但是,如果遇到需要与类加载器交互的情况,而您又不太了解类加载器的机制,就很容易花费大量时间调试异常,如 ClassNotFoundException 和 NoClassDefFoundError。本文将详细介绍 Java 的类加载器,帮助读者深入理解 Java 语言中的这一重要概念。下面先介绍一些基本概念。 类加载器的基本概念 顾名思义,类加载器用于将 Java 类加载到 Java 虚拟机中。一般来说,Java 虚拟机以如下方式使用 Java 类:Java 源程序(.java 文件)经 Java 编译器编译后转换为 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码并将其转换为 java.lang 实例。每个实例都用来表示一个 Java 类。通过该实例的 newInstance 方法创建该类的对象。实际情况可能更加复杂,例如,Java 字节代码可能是由工具动态生成或通过网络下载的。 基本上,所有类加载器都是 java.lang.ClassLoader 类的实例。下面将详细介绍这个 Java 类。 java.lang.ClassLoader 类简介 java.lang.ClassLoader 类的基本职责是根据给定类的名称为其查找或生成相应的字节码,然后根据这些字节码定义一个 Java 类,即 java.lang.Class 类的实例。除此之外,ClassLoader 还负责加载 Java 应用程序所需的资源,如图像文件和配置文件。不过,本文只讨论它加载类的功能。为了履行加载类的职责,ClassLoader 提供了许多方法,其中比较重要的方法如表 1 所示。下文将详细介绍这些方法。 表 1.与加载类相关的 ClassLoader 方法
-
在 JavaScript 中输出标签的方法