JavaScript中的創(chuàng)建枚舉四種方式
字符串和數(shù)字具有無數(shù)個(gè)值,而其他類型如布爾值則是有限的集合。
一周的日子(星期一,星期二,...,星期日),一年的季節(jié)(冬季,春季,夏季,秋季)和基本方向(北,東,南,西)都是具有有限值集合的例子。
當(dāng)一個(gè)變量有一個(gè)來自有限的預(yù)定義常量的值時(shí),使用枚舉是很方便的。枚舉使你不必使用魔法數(shù)字和字符串(這被認(rèn)為是一種反模式)。
讓我們看看在JavaScript中創(chuàng)建枚舉的四種好方法(及其優(yōu)缺點(diǎn))。
基于對(duì)象的枚舉
枚舉是一種數(shù)據(jù)結(jié)構(gòu),它定義了一個(gè)有限的具名常量集。每個(gè)常量都可以通過其名稱來訪問。
讓我們來考慮一件T恤衫的尺寸:Small
,Medium
,和Large
。
在JavaScript中創(chuàng)建枚舉的一個(gè)簡單方法(雖然不是最理想的)是使用一個(gè)普通的JavaScript對(duì)象。
const Sizes = { Small: 'small', Medium: 'medium', Large: 'large', } const mySize = Sizes.Medium console.log(mySize === Sizes.Medium) // logs true
Sizes
是一個(gè)基于JavaScript對(duì)象的枚舉,它有三個(gè)具名常量:Sizes.Small
、Sizes.Medium
以及Sizes.Large
。
Sizes
也是一個(gè)字符串枚舉,因?yàn)榫呙A康闹凳亲址?code>'small' ,'medium'
,以及 'large'
。
要訪問具名常量值,請(qǐng)使用屬性訪問器。例如,Sizes.Medium
的值是'medium'
。
枚舉的可讀性更強(qiáng),更明確,并消除了對(duì)魔法字符串或數(shù)字的使用。
優(yōu)缺點(diǎn)
普通的對(duì)象枚舉之所以吸引人,是因?yàn)樗芎唵危褐灰x一個(gè)帶有鍵和值的對(duì)象,枚舉就可以了。
但是在一個(gè)大的代碼庫中,有人可能會(huì)意外地修改枚舉對(duì)象,這將影響應(yīng)用程序的運(yùn)行。
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
!
普通對(duì)象的實(shí)現(xiàn)沒有受到保護(hù),因此無法避免這種意外的改變。
讓我們仔細(xì)看看字符串和symbol
枚舉。以及如何凍結(jié)枚舉對(duì)象以避免意外改變的問題。
枚舉值類型
除了字符串類型,枚舉值可以是一個(gè)數(shù)字:
const Sizes = { Small: 0, Medium: 1, Large: 2 } const mySize = Sizes.Medium console.log(mySize === Sizes.Medium) // logs true
上述例子中,Sizes
枚舉是數(shù)值枚舉,因?yàn)橹刀际菙?shù)字:0,1,2。
你也可以創(chuàng)建symbol
枚舉:
const Sizes = { Small: Symbol('small'), Medium: Symbol('medium'), Large: Symbol('large') } const mySize = Sizes.Medium console.log(mySize === Sizes.Medium) // logs true
使用symbol
的好處是,每個(gè)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
枚舉的缺點(diǎn)是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 '{}'
在下面的例子中,我將使用字符串枚舉。但是你可以自由地使用你需要的任何值類型。
如果你可以自由選擇枚舉值類型,就用字符串吧。字符串比數(shù)字和symbol
更容易進(jìn)行調(diào)試。
基于Object.freeze()枚舉
保護(hù)枚舉對(duì)象不被修改的一個(gè)好方法是凍結(jié)它。當(dāng)一個(gè)對(duì)象被凍結(jié)時(shí),你不能修改或向該對(duì)象添加新的屬性。換句話說,這個(gè)對(duì)象變成了只讀。
在JavaScript中,Object.freeze()
工具函數(shù)可以凍結(jié)一個(gè)對(duì)象。讓我們來凍結(jié)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({ ... })
創(chuàng)建一個(gè)凍結(jié)的對(duì)象。即使被凍結(jié),你也可以自由地訪問枚舉值: const mySize = Sizes.Medium
。
優(yōu)缺點(diǎn)
如果一個(gè)枚舉屬性被意外地改變了,JavaScript會(huì)拋出一個(gè)錯(cuò)誤(在嚴(yán)格模式下):
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'
對(duì) Sizes.Medium
屬性進(jìn)行了意外的賦值。
因?yàn)?code>Sizes是一個(gè)凍結(jié)的對(duì)象,JavaScript(在嚴(yán)格模式下)會(huì)拋出錯(cuò)誤:
TypeError: Cannot assign to read only property 'Medium' of object <Object>
凍結(jié)的對(duì)象枚舉被保護(hù)起來,不會(huì)被意外地改變。
不過,還有一個(gè)問題。如果你不小心把枚舉常量拼錯(cuò)了,那么結(jié)果將是未undefined
:
const Sizes = Object.freeze({ Small: 'small', Medium: 'medium', Large: 'large', }) console.log(Sizes.Med1um) // logs undefined
Sizes.Med1um
表達(dá)式(Med1um
是Medium
的錯(cuò)誤拼寫版本)結(jié)果為未定義,而不是拋出一個(gè)關(guān)于不存在的枚舉常量的錯(cuò)誤。
讓我們看看基于代理的枚舉如何解決這個(gè)問題。
基于proxy枚舉
一個(gè)有趣的,也是我最喜歡的實(shí)現(xiàn),是基于代理的枚舉。
代理是一個(gè)特殊的對(duì)象,它包裹著一個(gè)對(duì)象,以修改對(duì)原始對(duì)象的操作行為。代理并不改變?cè)紝?duì)象的結(jié)構(gòu)。
枚舉代理攔截對(duì)枚舉對(duì)象的讀和寫操作,并且:
- 當(dāng)訪問一個(gè)不存在的枚舉值時(shí),會(huì)拋出一個(gè)錯(cuò)誤。
- 當(dāng)一個(gè)枚舉對(duì)象的屬性被改變時(shí)拋出一個(gè)錯(cuò)誤
下面是一個(gè)工廠函數(shù)的實(shí)現(xiàn),它接受一個(gè)普通枚舉對(duì)象,并返回一個(gè)代理對(duì)象:
// 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()
方法攔截讀取操作,如果屬性名稱不存在,則拋出一個(gè)錯(cuò)誤。
set()
方法攔截寫操作,但只是拋出一個(gè)錯(cuò)誤。這是為保護(hù)枚舉對(duì)象不被寫入操作而設(shè)計(jì)的。
讓我們把sizes
對(duì)象枚舉包裝成一個(gè)代理:
import { Enum } from './enum' const Sizes = Enum({ Small: 'small', Medium: 'medium', Large: 'large', }) const mySize = Sizes.Medium console.log(mySize === Sizes.Medium) // logs true
代理枚舉的工作方式與普通對(duì)象枚舉完全一樣。
優(yōu)缺點(diǎn)
然而,代理枚舉受到保護(hù),以防止意外覆蓋或訪問不存在的枚舉常量:
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
拋出一個(gè)錯(cuò)誤,因?yàn)?code>Med1um常量名稱在枚舉中不存在。
Sizes.Medium = 'foo'
拋出一個(gè)錯(cuò)誤,因?yàn)槊杜e屬性已被改變。
代理枚舉的缺點(diǎn)是,你總是要導(dǎo)入枚舉工廠函數(shù),并將你的枚舉對(duì)象包裹在其中。
基于類的枚舉
另一種有趣的創(chuàng)建枚舉的方法是使用一個(gè)JavaScript類。
一個(gè)基于類的枚舉包含一組靜態(tài)字段,其中每個(gè)靜態(tài)字段代表一個(gè)枚舉的常量。每個(gè)枚舉常量的值本身就是該類的一個(gè)實(shí)例。
讓我們用一個(gè)Sizes
類來實(shí)現(xiàn)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
是一個(gè)代表枚舉的類。枚舉常量是該類的靜態(tài)字段,例如,static Small = new Sizes('small')
。
Sizes
類的每個(gè)實(shí)例也有一個(gè)私有字段#value
,它代表枚舉的原始值。
基于類的枚舉的一個(gè)很好的優(yōu)點(diǎn)是能夠在運(yùn)行時(shí)使用instanceof
操作來確定值是否是枚舉。例如,mySize instanceof Sizes
結(jié)果為真,因?yàn)?code>mySize是一個(gè)枚舉值。
基于類的枚舉比較是基于實(shí)例的(而不是在普通、凍結(jié)或代理枚舉的情況下的原始比較):
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
,也是不同的對(duì)象實(shí)例。
優(yōu)缺點(diǎn)
基于類的枚舉不能受到保護(hù),以防止覆蓋或訪問不存在的枚舉具名常量。
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
但你可以控制新實(shí)例的創(chuàng)建,例如,通過計(jì)算在構(gòu)造函數(shù)內(nèi)創(chuàng)建了多少個(gè)實(shí)例。然后在創(chuàng)建超過3個(gè)實(shí)例時(shí)拋出一個(gè)錯(cuò)誤。
當(dāng)然,最好讓你的枚舉實(shí)現(xiàn)盡可能的簡單。枚舉的目的是為了成為普通的數(shù)據(jù)結(jié)構(gòu)。
總結(jié)
在JavaScript中,有4種創(chuàng)建枚舉的好方法。
最簡單的方法是使用一個(gè)普通的JavaScript對(duì)象:
const MyEnum = { Option1: 'option1', Option2: 'option2', Option3: 'option3' }
普通的對(duì)象枚舉適合小型項(xiàng)目或快速演示。
第二種選擇,如果你想保護(hù)枚舉對(duì)象不被意外覆蓋,則可以使用凍結(jié)的對(duì)象:
const MyEnum = Object.freeze({ Option1: 'option1', Option2: 'option2', Option3: 'option3' })
凍結(jié)的對(duì)象枚舉適合于中型或大型項(xiàng)目,你要確保枚舉不會(huì)被意外地改變。
第三種選擇是代理方法:
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' })
代理枚舉適用于中型或大型項(xiàng)目,以更好地保護(hù)你的枚舉不被覆蓋或訪問不存在的命名常量。
代理的枚舉是我個(gè)人的偏好。
第四種選擇是使用基于類的枚舉,其中每個(gè)命名的常量都是類的實(shí)例,并作為類的靜態(tài)屬性被存儲(chǔ):
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 } }
如果你喜歡類的話,基于類的枚舉是可行的。然而,基于類的枚舉比凍結(jié)的或代理的枚舉保護(hù)得更少。
你還知道哪些在JavaScript中創(chuàng)建枚舉的方法?
到此這篇關(guān)于JavaScript中的四種枚舉方式的文章就介紹到這了,更多相關(guān)js枚舉方式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript自定義startWith()和endWith()的兩種方法
js中自定義startWith()和endWith()方法有兩種,在本文將為大家詳細(xì)介紹下,感興趣的朋友不要錯(cuò)過2013-11-11Javascript實(shí)現(xiàn)獲取窗口的大小和位置代碼分享
這篇文章主要分享了一段Javascript實(shí)現(xiàn)獲取窗口的大小和位置代碼,兼容性非常好,這里推薦給大家2014-12-12javascript下4個(gè)跨瀏覽器必備的函數(shù)
如果你的項(xiàng)目要用到 JavaScript,而你不使用任何 JavaScript 框架,那么對(duì)于那些常用且各個(gè)瀏覽器明顯不同的地方就需要用函數(shù)來封裝起來。2010-03-03淺析JS中的 map, filter, some, every, forEach, for in, for of 用法總
本文是小編給大家總結(jié)的關(guān)于javascript中的map, filter, some, every, forEach, for in, for of 用法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-03-03JavaScript實(shí)現(xiàn)元素滾動(dòng)條到達(dá)一定位置循環(huán)追加內(nèi)容
下面小編就為大家分享一篇JavaScript實(shí)現(xiàn)元素滾動(dòng)條到達(dá)一定位置循環(huán)追加內(nèi)容,具有很好的參考價(jià)值,希望對(duì)大家有所幫助2017-12-12