JavaScript實現(xiàn)枚舉的幾種方法總結(jié)
基于普通對象
我們來考慮一個場景:T恤的尺寸:Small、Medium、Large三種類型,那么我們使用普通對象的方式來實現(xiàn),代碼如下:
const Sizes = {
Small: 'small',
Medium: 'medium',
Large: 'large',
}基于這種方式實現(xiàn)一個枚舉是非常簡單的,而且也足夠的清晰,此時Sizes就是一個基于JavaScript普通對象的枚舉,它有3個命名常量:Size.Small、Size.Medium、Size.Large,我們可以通過Size.small來獲取對應(yīng)的枚舉值。
優(yōu)點
- 簡單:使用這種方式實現(xiàn)枚舉是非常簡單,只需要定義一個帶有鍵和值的對象即可
缺點
- 容易被外部修改
當(dāng)我們在維護(hù)一個大型的代碼倉庫時,枚舉值可能會被意外更改,代碼如下:
const size1 = Sizes.Medium const size2 = Sizes.Medium = 'foo' // Changed! console.log(size1 === Sizes.Medium) // logs false
如代碼中所示,當(dāng)枚舉被修改后,之后的邏輯將會出現(xiàn)問題,這也就是說,我們實現(xiàn)枚舉的時候需要考慮對象的值不能被修改。
基于Symbol
我們也可以這樣實現(xiàn)一個枚舉:
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使用這種方式實現(xiàn)的枚舉看上去和機(jī)遇普通對象的方式類似,只是把對象中的值修改成了Symbol類型,那么這樣和剛才的方式又有什么不同呢?
優(yōu)點
- 必須使用枚舉本身來進(jìn)行比較:也就是上面代碼中我們必須使用
Sizes.Large來比較,再重新創(chuàng)建一個Symbol('large')對比的話則不相等,而對比第一種方法,只要字符串相同即為相同,這種對比方式更加嚴(yán)格了
缺點
- 不能使用JSON.stringify,使用
JSON.stringify會將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 })) // {}- 容易被外部修改,這點與第一種方案的情況類似,接下來,我們將介紹如何能夠保證枚舉值不會被修改的方案
基于Object.freeze
使用Object.freeze可以使一個對象被凍結(jié):被凍結(jié)的對象不能再被更改:不能添加新的屬性,不能移除現(xiàn)有的屬性,不能更改它們的可枚舉性、可配置性、可寫性或值,對象的原型也不能被重新指定。我們使用這種實現(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)格模式下,這種賦值的情況還會拋出錯誤。
優(yōu)點
- 有效防止枚舉值被修改
缺點
- 當(dāng)拼寫錯誤時,會直接返回
undefined,比如我們直接獲取Sizes.a,此時會返回undefined,這個問題在前面的幾個方案中也是同樣的,我們在開發(fā)過程中,應(yīng)該是更希望拋出一個錯誤,這樣在開發(fā)階段更直接的發(fā)現(xiàn)問題所在。于是,就有了下面一種方案。
基于Proxy
使用Proxy用于創(chuàng)建一個對象的代理,從而實現(xiàn)基本操作的攔截和自定義,Proxy并不會改變原始對象的結(jié)構(gòu),而且我們可以實現(xiàn)如下兩個需求:
- 訪問不存在枚舉時,拋出錯誤
- 修改枚舉對象屬性時,拋出錯誤
這樣,就可以同時滿足我們前面幾種方案遇到的問題了,接下來,我們封裝一個函數(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')
}
})
}這個函數(shù)中,我們傳入一個初始枚舉對象,當(dāng)我們訪問某個屬性的時候,如果沒有將會拋出錯誤"${name}" value does not exist in the enum,當(dāng)我們修改值的時候,也會拋出一個錯誤Cannot add a new value to the enum接下來,我們使用這個函數(shù)包裝一下Sizes,代碼如下:
const Sizes = Enum({
Small: 'small',
Medium: 'medium',
Large: 'large',
})
const mySize = Sizes.Large; // large
Sizes.Small = '1' // 拋出錯誤: Cannot add a new value to the enum
console.log(Sizes.a) // 拋出錯誤:"a" value does not exist in the enum優(yōu)點
- 枚舉值防止修改
- 訪問不存在的枚舉時會拋出錯誤
缺點
- 相對復(fù)雜,必須導(dǎo)入
Enum函數(shù)
基于Class
另一個方法是基于JavaScript中的Class類實現(xiàn)的,這個類中包含一組靜態(tài)的字段,而每一個對應(yīng)的值本身又是這個實例,代碼如下:
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;
}
}每一個枚舉值都是一個Sizes實例,內(nèi)部有個私有屬性#value, 用來表示枚舉的原始值。我們舉幾個例子來看下使用這種方式實現(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)點
- 可以通過
instanceof來判斷是否是枚舉:上面例子中我們可以判斷出來mySize是一個枚舉 - 這種方式枚舉的對比是基于實例的:上面例子中
mySize === new Sizes('large'),即使是相同的#value,也是不同的實例
缺點
- 枚舉值可能會被意外修改
- 訪問不存在的枚舉時不會拋出錯誤
總結(jié)
上面我們介紹了幾種在JavaScript中實現(xiàn)枚舉的方式,每種方式都有各自的優(yōu)缺點,相比之下,我認(rèn)為:
- Proxy方式更為靈活,可以按照自己的需求進(jìn)行更多的定制化;
- 如果枚舉值用的較多,且項目較大,選擇Object.freeze方式,防止枚舉值被意外修改;
- 如果您遇到的情況相對簡單,使用基于普通對象的方式;
總之,我們最終的實現(xiàn)要盡量的簡單,不要過度設(shè)計,按照具體情況選擇合適的方式。
相關(guān)文章
javascript動態(tài)的改變IFrame的高度實現(xiàn)自動伸展
動態(tài)的改變IFrame的高度,實現(xiàn)IFrame自動伸展,父頁面也自動神縮,實現(xiàn)原理很簡單就是在IFrame子頁面一加載的時候,調(diào)用父IFrame對象,改變其高度,感興趣的朋友可以了解下2013-10-10
JS+Ajax+Jquery實現(xiàn)頁面無刷新分頁以及分組 超強(qiáng)的實現(xiàn)
JS+Ajax+Jquery實現(xiàn)頁面無刷新分頁以及分組 超強(qiáng)的實現(xiàn) 加上你的CSS完全可以與EXT媲美哦2009-08-08
bootstrap datepicker限定可選時間范圍實現(xiàn)方法
這篇文章主要介紹了bootstrap datepicker限定可選時間范圍的實現(xiàn)方法,本文涉及到相關(guān)知識點,通過實例給大家介紹的非常詳細(xì),需要的朋友可以參考下2016-09-09

