如 TypeScript 官方文檔所說(shuō),枚舉類型是對(duì) JavaScript 標(biāo)準(zhǔn)數(shù)據(jù)類型集的擴(kuò)充梢灭。對(duì)于熟悉 C# 的開發(fā)者來(lái)說(shuō)夷家,枚舉類型并不陌生,它能夠給一系列數(shù)值集合提供友好的名稱敏释,也就是說(shuō)枚舉表示的是一個(gè)命名元素的集合库快,因而它能夠使您的程序避免因硬編碼的值而顯雜亂且難以維護(hù)。
今天钥顽,我們將圍繞枚舉類型展開以下幾個(gè)內(nèi)容:
- 枚舉基礎(chǔ)
- 背后的 JavaScript 及其可擴(kuò)充性
- 常量枚舉
- 最佳實(shí)踐
枚舉基礎(chǔ)
默認(rèn)情況下义屏,枚舉是基于 0 的,也就是說(shuō)第一個(gè)值是 0蜂大,后面的值依次遞增闽铐。不要擔(dān)心,當(dāng)中的每一個(gè)值都可以顯式指定县爬,只要不出現(xiàn)重復(fù)即可阳啥,沒有被顯式指定的值,都會(huì)在前一個(gè)值的基礎(chǔ)上遞增财喳。
enum Color {Red, Green, Blue}
let c: Color = Color.Green; // 1
或
enum Color {Red = 1, Green, Blue = 4}
let c: Color = Color.Green; // 2
枚舉有一個(gè)很方便的特性察迟,就是您也可以向枚舉傳遞一個(gè)數(shù)值,然后獲取它對(duì)應(yīng)的名稱值耳高。舉個(gè)例子扎瓶,如果我們有一個(gè)值 2
,但是不清楚在 Color
枚舉中與之對(duì)應(yīng)的名稱是什么泌枪,我們就可以通過以下的方式來(lái)進(jìn)行檢索:
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2]; // 'Green'
但是像上面的這種寫法不是太好概荷,因?yàn)槿绻o定的數(shù)值沒有與之對(duì)應(yīng)的枚舉項(xiàng),那么結(jié)果就是 undefined
碌燕。所以误证,如果您想要得到指定枚舉項(xiàng)的字符串名稱,可以使用類似這樣的寫法:
let colorName: string = Color[Color.Green]; // 'Green'
背后的 JavaScript
不知道大家有沒有好奇過修壕,既然 JavaScript 中沒有枚舉類型愈捅,那么 TypeScript 是怎么編譯枚舉類型的,以使其成為一個(gè)合法的 JavaScript 數(shù)據(jù)類型〈瑞現(xiàn)在蓝谨,我們就以上面提到的枚舉的特性一步步分析:
- 我們可以通過
.
號(hào)訪問枚舉的內(nèi)容,這與訪問對(duì)象屬性的方式一樣,那么我們是否可以將其認(rèn)為一個(gè)對(duì)象譬巫,這樣就可以得到以下的結(jié)構(gòu):
var Color = {
Red: 0,
Green: 1,
Blue: 2
};
- 我們也可以通過具體的數(shù)值獲取其對(duì)象的名稱咖楣,這種操作和數(shù)組非常相似,傳遞索引值芦昔,提取具體索引位的值诱贿,這樣順理成章得出以下的結(jié)構(gòu):
var Color = ['Red', 'Green', 'Blue'];
- 現(xiàn)在將上面提到的兩個(gè)想法合并起來(lái),那么現(xiàn)在很多人可能傻眼了咕缎,#1 推導(dǎo)出來(lái)的是對(duì)象瘪松,#2 推導(dǎo)出來(lái)的是數(shù)組,這兩者怎么結(jié)合起來(lái)锨阿?不要著急宵睦,想一想,數(shù)組可是一種特殊的對(duì)象墅诡,我們將推導(dǎo)寫在下面的代碼中:
// 因?yàn)閿?shù)組也是一種對(duì)象壳嚎,那么全部都用對(duì)象表示
var Color = {};
// 換一種方式改寫 #1 中的對(duì)象
Color['Red'] = 0;
Color['Green'] = 1;
Color['Blue'] = 2;
// 換一種方式改寫 #2 中的數(shù)組
Color[0] = 'Red';
Color[1] = 'Green';
Color[2] = 'Blue';
- 看完上面的代碼,是否感覺豁然開朗末早,現(xiàn)在我們繼續(xù)將上面的代碼精簡(jiǎn)一下:
var Color = {};
Color[Color['Red'] = 0] = 'Red';
Color[Color['Green'] = 1] = 'Green';
Color[Color['Blue'] = 2] = 'Blue';
- 現(xiàn)在我們看一下烟馅,如果是 TypeScript,它會(huì)將
enum Color {Red, Green, Blue}
編譯為什么樣的 JavaScript 代碼:
var Color;
(function (Color) {
Color[Color["Red"] = 0] = "Red";
Color[Color["Green"] = 1] = "Green";
Color[Color["Blue"] = 2] = "Blue";
})(Color || (Color = {}));
枚舉的可擴(kuò)充性
注意到上面生成的代碼最后一行有沒有:(Color || (Color = {}))
然磷,這就意味著郑趁,如果您代碼運(yùn)行的當(dāng)前作用域中已經(jīng)存在了 Color
變量,那么就會(huì)直接在當(dāng)前 Color
變量上面擴(kuò)展(如果我們是以模塊的方式組織代碼姿搜,就不用擔(dān)心此種情況)寡润。
基于這個(gè)特性,我們可以在多個(gè)文件中分散聲明我們的枚舉類型舅柜,比如:
enum Color {
Red,
Green,
Blue
}
enum Color {
DarkRed = 3,
DarkGreen,
DarkBlue
}
編譯后:
var Color;
(function (Color) {
Color[Color["Red"] = 0] = "Red";
Color[Color["Green"] = 1] = "Green";
Color[Color["Blue"] = 2] = "Blue";
})(Color || (Color = {}));
(function (Color) {
Color[Color["DarkRed"] = 3] = "DarkRed";
Color[Color["DarkGreen"] = 4] = "DarkGreen";
Color[Color["DarkBlue"] = 5] = "DarkBlue";
})(Color || (Color = {}));
如果我們采用此種寫法梭纹,千萬(wàn)要注意每一部分枚舉項(xiàng)值的設(shè)置,以防止值沖突的出現(xiàn)致份。
常量枚舉
如果我們?cè)诼暶髅杜e類型的時(shí)候变抽,前面加上 const
關(guān)鍵字,那么這個(gè)枚舉就是常量枚舉氮块。但是此種枚舉類型與上面提到的不一樣绍载,不一樣在哪里呢?看下面代碼及編譯后的結(jié)果:
const enum Color {
Red,
Green,
Blue
}
let c: Color = Color.Green;
編譯后:
var c = 1 /* Green */;
哇滔蝉,只剩下簡(jiǎn)單的一行代碼了击儡,Color
變量沒了!也就是锰提,編譯器針對(duì)此種枚舉類型做出了如下處理:
- 內(nèi)聯(lián)所有的枚舉使用(也就是直接內(nèi)聯(lián)此枚舉項(xiàng)的值)曙痘。
- 不為枚舉類型生成其對(duì)應(yīng)的 JavaScript 代碼(在這里也就是不再生成
Color
變量并為其初始化)。
使用常量枚舉立肘,還有一點(diǎn)需要注意的是边坤,我們沒法像 Color[Color.Green]
這樣獲取此枚舉項(xiàng)的字符串名稱,這也歸咎于沒有枚舉類型的 JavaScript 代碼所導(dǎo)致的谅年。
最佳實(shí)踐
1. 首個(gè)枚舉項(xiàng)值設(shè)置為 1
我們現(xiàn)在都知道茧痒,首個(gè)枚舉項(xiàng)的默認(rèn)值為 0。這點(diǎn)值得注意融蹂,在 JavaScript 中旺订,0 是一個(gè)假值,那么就會(huì)出現(xiàn)一個(gè)問題超燃,如果我想要判斷一個(gè)枚舉值是否存在怎么辦区拳?我能直接 if (c) { ... }
這樣判斷嗎?顯然是不可靠的意乓,請(qǐng)看下面代碼:
enum Color {Red, Green, Blue}
let c: Color;
if (!c) {
console.log('Yes, I am not defined.');
}
c = Color.Red;
if (!c) {
console.log('Oops, I have a valid value, but seems I am undefined!');
}
是不是樱调,問題顯而易見,如果我們想要對(duì)此枚舉類型變量值進(jìn)行判斷届良,我們還得這么寫:typeof(c) !== 'undefined'
笆凌,非常繁瑣。而如果將首個(gè)枚舉項(xiàng)的值設(shè)置為 1士葫,我們就可以放心地直接將變量放在條件判斷語(yǔ)句中進(jìn)行判斷了乞而。
2. 盡量不要為枚舉項(xiàng)手動(dòng)設(shè)置值
好像這一點(diǎn)與上面說(shuō)的很矛盾啊,其實(shí)這一條的重點(diǎn)是慢显,不要為多個(gè)枚舉項(xiàng)設(shè)置值爪模,特別是在枚舉項(xiàng)特別多的情況下,因?yàn)檫@很容易出錯(cuò)荚藻,如下面代碼所示:
enum Color {
Red,
Orange = 2,
Yellow,
Green,
Blue,
Indigo = 5,
Purple
}
console.log(Color.Blue !== Color.Indigo ? 'yes' : 'no'); // 'no'
很不幸的是呻右,您原本認(rèn)為這兩個(gè)值不一樣,可結(jié)果是一樣的鞋喇,這是由于您的疏忽而導(dǎo)致的(如果 TypeScript 能夠?yàn)榇颂峁彶榈臋C(jī)制声滥,并在編譯時(shí)報(bào)錯(cuò),我們就很大程度上避免犯這樣的錯(cuò))侦香。我們可以繼續(xù)看一下編譯后的結(jié)果:
var Color;
(function (Color) {
Color[Color["Red"] = 0] = "Red";
Color[Color["Orange"] = 2] = "Orange";
Color[Color["Yellow"] = 3] = "Yellow";
Color[Color["Green"] = 4] = "Green";
Color[Color["Blue"] = 5] = "Blue";
Color[Color["Indigo"] = 5] = "Indigo";
Color[Color["Purple"] = 6] = "Purple";
})(Color || (Color = {}));
同樣落塑,Color[5]
獲取的值也具有歧義,程序的穩(wěn)定性也因此受到影響罐韩,所以如果不是出于某種特殊目的憾赁,就不要手動(dòng)去設(shè)置值了。
3. 不要為枚舉項(xiàng)設(shè)置字符串?dāng)?shù)據(jù)類型
您可能會(huì)好奇了散吵,難道除了數(shù)字類型龙考,還能設(shè)置字符串類型蟆肆?是的,的確可以晦款。但是我為什么不推薦這么做呢炎功?我們看一下下面代碼編譯后的結(jié)果就知道了:
enum Color {
Red,
Green,
Blue = 'Blue Color'
}
編譯后:
var Color;
(function (Color) {
Color[Color["Red"] = 0] = "Red";
Color[Color["Green"] = 1] = "Green";
Color["Blue"] = "Blue Color";
})(Color || (Color = {}));
不難發(fā)現(xiàn),我們沒法像 Color[1]
這樣使用 Color['Blue Color']
了缓溅,這樣就導(dǎo)致行為結(jié)果與預(yù)期的不一致性蛇损,其次,如果像下面這樣寫坛怪,TypeScript 還會(huì)報(bào)錯(cuò)淤齐,因?yàn)橄旅娴闹禌]法根據(jù)上面的字符串值繼續(xù)遞增或推導(dǎo)了:
enum Color {
Red,
Green = 'Green Color',
Blue // error: Enum member must have initializer.
}
所以這里強(qiáng)烈不建議將枚舉項(xiàng)的值設(shè)置為字符串類型。