JavaScript實(shí)現(xiàn)枚舉的幾種方法總結(jié)
基于普通對(duì)象
我們來考慮一個(gè)場(chǎng)景:T恤的尺寸:Small、Medium、Large
三種類型,那么我們使用普通對(duì)象的方式來實(shí)現(xiàn),代碼如下:
const Sizes = { Small: 'small', Medium: 'medium', Large: 'large', }
基于這種方式實(shí)現(xiàn)一個(gè)枚舉是非常簡(jiǎn)單的,而且也足夠的清晰,此時(shí)Sizes
就是一個(gè)基于JavaScript
普通對(duì)象的枚舉,它有3個(gè)命名常量:Size.Small
、Size.Medium
、Size.Large
,我們可以通過Size.small
來獲取對(duì)應(yīng)的枚舉值。
優(yōu)點(diǎn)
- 簡(jiǎn)單:使用這種方式實(shí)現(xiàn)枚舉是非常簡(jiǎn)單,只需要定義一個(gè)帶有鍵和值的對(duì)象即可
缺點(diǎn)
- 容易被外部修改
當(dāng)我們?cè)诰S護(hù)一個(gè)大型的代碼倉庫時(shí),枚舉值可能會(huì)被意外更改,代碼如下:
const size1 = Sizes.Medium const size2 = Sizes.Medium = 'foo' // Changed! console.log(size1 === Sizes.Medium) // logs false
如代碼中所示,當(dāng)枚舉被修改后,之后的邏輯將會(huì)出現(xiàn)問題,這也就是說,我們實(shí)現(xiàn)枚舉的時(shí)候需要考慮對(duì)象的值不能被修改。
基于Symbol
我們也可以這樣實(shí)現(xiàn)一個(gè)枚舉:
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
使用這種方式實(shí)現(xiàn)的枚舉看上去和機(jī)遇普通對(duì)象的方式類似,只是把對(duì)象中的值修改成了Symbol
類型,那么這樣和剛才的方式又有什么不同呢?
優(yōu)點(diǎn)
- 必須使用枚舉本身來進(jìn)行比較:也就是上面代碼中我們必須使用
Sizes.Large
來比較,再重新創(chuàng)建一個(gè)Symbol('large')
對(duì)比的話則不相等,而對(duì)比第一種方法,只要字符串相同即為相同,這種對(duì)比方式更加嚴(yán)格了
缺點(diǎn)
- 不能使用JSON.stringify,使用
JSON.stringify
會(huì)將Symbol
轉(zhuǎn)為undefined、null或直接跳過,代碼如下:
console.log(JSON.stringify(Sizes.Small)); // undefined console.log(JSON.stringify([Sizes.Small])); // [null] console.log(JSON.stringify({ size: Sizes.Small })) // {}
- 容易被外部修改,這點(diǎn)與第一種方案的情況類似,接下來,我們將介紹如何能夠保證枚舉值不會(huì)被修改的方案
基于Object.freeze
使用Object.freeze可以使一個(gè)對(duì)象被凍結(jié):被凍結(jié)的對(duì)象不能再被更改:不能添加新的屬性,不能移除現(xiàn)有的屬性,不能更改它們的可枚舉性、可配置性、可寫性或值,對(duì)象的原型也不能被重新指定。我們使用這種實(shí)現(xiàn)枚舉,代碼如下:
const Sizes = Object.freeze({ Small: 'small', Medium: 'medium', Large: 'large', }) const mySize = Sizes.Large; Sizes.Large = '111' console.log(mySize === Sizes.Large) // true
使用這種方式,就算去修改枚舉值也是無效的,如果在嚴(yán)格模式下,這種賦值的情況還會(huì)拋出錯(cuò)誤。
優(yōu)點(diǎn)
- 有效防止枚舉值被修改
缺點(diǎn)
- 當(dāng)拼寫錯(cuò)誤時(shí),會(huì)直接返回
undefined
,比如我們直接獲取Sizes.a
,此時(shí)會(huì)返回undefined
,這個(gè)問題在前面的幾個(gè)方案中也是同樣的,我們?cè)陂_發(fā)過程中,應(yīng)該是更希望拋出一個(gè)錯(cuò)誤,這樣在開發(fā)階段更直接的發(fā)現(xiàn)問題所在。于是,就有了下面一種方案。
基于Proxy
使用Proxy用于創(chuàng)建一個(gè)對(duì)象的代理,從而實(shí)現(xiàn)基本操作的攔截和自定義,Proxy并不會(huì)改變?cè)紝?duì)象的結(jié)構(gòu),而且我們可以實(shí)現(xiàn)如下兩個(gè)需求:
- 訪問不存在枚舉時(shí),拋出錯(cuò)誤
- 修改枚舉對(duì)象屬性時(shí),拋出錯(cuò)誤
這樣,就可以同時(shí)滿足我們前面幾種方案遇到的問題了,接下來,我們封裝一個(gè)函數(shù),代碼如下:
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') } }) }
這個(gè)函數(shù)中,我們傳入一個(gè)初始枚舉對(duì)象,當(dāng)我們?cè)L問某個(gè)屬性的時(shí)候,如果沒有將會(huì)拋出錯(cuò)誤"${name}" value does not exist in the enum
,當(dāng)我們修改值的時(shí)候,也會(huì)拋出一個(gè)錯(cuò)誤Cannot add a new value to the enum
接下來,我們使用這個(gè)函數(shù)包裝一下Sizes
,代碼如下:
const Sizes = Enum({ Small: 'small', Medium: 'medium', Large: 'large', }) const mySize = Sizes.Large; // large Sizes.Small = '1' // 拋出錯(cuò)誤: Cannot add a new value to the enum console.log(Sizes.a) // 拋出錯(cuò)誤:"a" value does not exist in the enum
優(yōu)點(diǎn)
- 枚舉值防止修改
- 訪問不存在的枚舉時(shí)會(huì)拋出錯(cuò)誤
缺點(diǎn)
- 相對(duì)復(fù)雜,必須導(dǎo)入
Enum
函數(shù)
基于Class
另一個(gè)方法是基于JavaScript
中的Class類實(shí)現(xiàn)的,這個(gè)類中包含一組靜態(tài)的字段,而每一個(gè)對(duì)應(yīng)的值本身又是這個(gè)實(shí)例,代碼如下:
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; } }
每一個(gè)枚舉值都是一個(gè)Sizes
實(shí)例,內(nèi)部有個(gè)私有屬性#value
, 用來表示枚舉的原始值。我們舉幾個(gè)例子來看下使用這種方式實(shí)現(xiàn)的具有哪些特性:
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
優(yōu)點(diǎn)
- 可以通過
instanceof
來判斷是否是枚舉:上面例子中我們可以判斷出來mySize
是一個(gè)枚舉 - 這種方式枚舉的對(duì)比是基于實(shí)例的:上面例子中
mySize === new Sizes('large')
,即使是相同的#value
,也是不同的實(shí)例
缺點(diǎn)
- 枚舉值可能會(huì)被意外修改
- 訪問不存在的枚舉時(shí)不會(huì)拋出錯(cuò)誤
總結(jié)
上面我們介紹了幾種在JavaScript中實(shí)現(xiàn)枚舉的方式,每種方式都有各自的優(yōu)缺點(diǎn),相比之下,我認(rèn)為:
- Proxy方式更為靈活,可以按照自己的需求進(jìn)行更多的定制化;
- 如果枚舉值用的較多,且項(xiàng)目較大,選擇Object.freeze方式,防止枚舉值被意外修改;
- 如果您遇到的情況相對(duì)簡(jiǎn)單,使用基于普通對(duì)象的方式;
總之,我們最終的實(shí)現(xiàn)要盡量的簡(jiǎn)單,不要過度設(shè)計(jì),按照具體情況選擇合適的方式。
相關(guān)文章
JavaScript中實(shí)現(xiàn)單體模式分享
這篇文章主要介紹了JavaScript中實(shí)現(xiàn)單體模式分享,單體模式的定義:?jiǎn)误w是一個(gè)用來劃分命名空間并將一批相關(guān)方法和屬性組織在一起的對(duì)象,如果它能夠被實(shí)例化,那么只能被實(shí)例化一次,需要的朋友可以參考下2015-01-01基于JavaScript實(shí)現(xiàn)購物網(wǎng)站商品放大鏡效果
大家在日常生活中都有網(wǎng)購的經(jīng)驗(yàn),有的網(wǎng)站會(huì)有商品放大鏡功能,效果非常棒,那么基于js代碼是如何實(shí)現(xiàn)的呢?下面小編給大家?guī)砹嘶趈s實(shí)現(xiàn)購物網(wǎng)站商品放大鏡效果,非常不錯(cuò),感興趣的朋友參考下吧2016-09-09JS判斷移動(dòng)端訪問設(shè)備并加載對(duì)應(yīng)CSS樣式
JS判斷不同web訪問環(huán)境,主要針對(duì)移動(dòng)設(shè)備,提供相對(duì)應(yīng)的解析方案,本例是加載不同的css樣式2014-06-06微信小程序?qū)崿F(xiàn)單個(gè)卡片左滑顯示按鈕并防止上下滑動(dòng)干擾功能
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)單個(gè)卡片左滑顯示按鈕并防止上下滑動(dòng)干擾功能,利用小程序事件處理的api,分別讀取觸摸開始,觸摸移動(dòng)時(shí),觸摸結(jié)束的X/Y坐標(biāo),根據(jù)差值來改變整個(gè)卡片的位置,具體實(shí)例代碼跟隨小編一起看看吧2019-12-12js跨域問題之跨域iframe自適應(yīng)大小實(shí)現(xiàn)代碼
前幾天做公司和開心網(wǎng)合作項(xiàng)目的時(shí)候 碰到iframe 跨域自適應(yīng)的問題剛開始很迷惑 開心網(wǎng)那邊技術(shù)工程師給我發(fā)了一段這樣子的代碼。2010-07-07