如 TypeScript 官方文檔所說,枚舉類型是對 JavaScript 標準數據類型集的擴充。對於熟悉 C# 的開發者來說,枚舉類型並不陌生,它能夠給一系列數值集合提供友好的名稱,也就是說枚舉表示的是一個命名元素的集合,因而它能夠使您的程序避免因硬編碼的值而顯雜亂且難以維護。
今天,我們將圍繞枚舉類型展開以下幾個內容:
- 枚舉基礎
- 背後的 JavaScript 及其可擴充性
- 常量枚舉
- 最佳實踐
枚舉基礎
默認情況下,枚舉是基於 0 的,也就是說第一個值是 0,後面的值依次遞增。不要擔心,當中的每一個值都可以顯式指定,只要不出現重複即可,沒有被顯式指定的值,都會在前一個值的基礎上遞增。
enum Color {Red, Green, Blue} let c: Color = Color.Green; // 1
或
enum Color {Red = 1, Green, Blue = 4} let c: Color = Color.Green; // 2
枚舉有一個很方便的特性,就是您也可以向枚舉傳遞一個數值,然後獲取它對應的名稱值。舉個例子,如果我們有一個值 2
,但是不清楚在 Color
枚舉中與之對應的名稱是什麼,我們就可以通過以下的方式來進行檢索:
enum Color {Red = 1, Green, Blue} let colorName: string = Color[2]; // 'Green'
但是像上面的這種寫法不是太好,因爲如果您給定的數值沒有與之對應的枚舉項,那麼結果就是 undefined
。所以,如果您想要得到指定枚舉項的字符串名稱,可以使用類似這樣的寫法:
let colorName: string = Color[Color.Green]; // 'Green'
背後的 JavaScript
不知道大家有沒有好奇過,既然 JavaScript 中沒有枚舉類型,那麼 TypeScript 是怎麼編譯枚舉類型的,以使其成爲一個合法的 JavaScript 數據類型。現在,我們就以上面提到的枚舉的特性一步步分析:
- 我們可以通過
.
號訪問枚舉的內容,這與訪問對象屬性的方式一樣,那麼我們是否可以將其認爲一個對象,這樣就可以得到以下的結構:
var Color = { Red: 0, Green: 1, Blue: 2 };
- 我們也可以通過具體的數值獲取其對象的名稱,這種操作和數組非常相似,傳遞索引值,提取具體索引位的值,這樣順理成章得出以下的結構:
var Color = ['Red', 'Green', 'Blue'];
- 現在將上面提到的兩個想法合併起來,那麼現在很多人可能傻眼了,#1 推導出來的是對象,#2 推導出來的是數組,這兩者怎麼結合起來?不要着急,想一想,數組可是一種特殊的對象,我們將推導寫在下面的代碼中:
// 因爲數組也是一種對象,那麼全部都用對象表示 var Color = {}; // 換一種方式改寫 #1 中的對象 Color['Red'] = 0; Color['Green'] = 1; Color['Blue'] = 2; // 換一種方式改寫 #2 中的數組 Color[0] = 'Red'; Color[1] = 'Green'; Color[2] = 'Blue';
- 看完上面的代碼,是否感覺豁然開朗,現在我們繼續將上面的代碼精簡一下:
var Color = {}; Color[Color['Red'] = 0] = 'Red'; Color[Color['Green'] = 1] = 'Green'; Color[Color['Blue'] = 2] = 'Blue';
- 現在我們看一下,如果是 TypeScript,它會將
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 = {}));
枚舉的可擴充性
注意到上面生成的代碼最後一行有沒有:(Color || (Color = {}))
,這就意味着,如果您代碼運行的當前作用域中已經存在了 Color
變量,那麼就會直接在當前 Color
變量上面擴展(如果我們是以模塊的方式組織代碼,就不用擔心此種情況)。
基於這個特性,我們可以在多個文件中分散聲明我們的枚舉類型,比如:
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 = {}));
如果我們採用此種寫法,千萬要注意每一部分枚舉項值的設置,以防止值衝突的出現。
常量枚舉
如果我們在聲明枚舉類型的時候,前面加上 const
關鍵字,那麼這個枚舉就是常量枚舉。但是此種枚舉類型與上面提到的不一樣,不一樣在哪裏呢?看下面代碼及編譯後的結果:
const enum Color { Red, Green, Blue } let c: Color = Color.Green;
編譯後:
var c = 1 /* Green */;
哇,只剩下簡單的一行代碼了,Color
變量沒了!也就是,編譯器針對此種枚舉類型做出瞭如下處理:
- 內聯所有的枚舉使用(也就是直接內聯此枚舉項的值)。
- 不爲枚舉類型生成其對應的 JavaScript 代碼(在這裏也就是不再生成
Color
變量併爲其初始化)。
使用常量枚舉,還有一點需要注意的是,我們沒法像 Color[Color.Green]
這樣獲取此枚舉項的字符串名稱,這也歸咎於沒有枚舉類型的 JavaScript 代碼所導致的。
最佳實踐
1. 首個枚舉項值設置爲 1
我們現在都知道,首個枚舉項的默認值爲 0。這點值得注意,在 JavaScript 中,0 是一個假值,那麼就會出現一個問題,如果我想要判斷一個枚舉值是否存在怎麼辦?我能直接 if (c) { ... }
這樣判斷嗎?顯然是不可靠的,請看下面代碼:
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!'); }
是不是,問題顯而易見,如果我們想要對此枚舉類型變量值進行判斷,我們還得這麼寫:typeof(c) !== 'undefined'
,非常繁瑣。而如果將首個枚舉項的值設置爲 1,我們就可以放心地直接將變量放在條件判斷語句中進行判斷了。
2. 儘量不要爲枚舉項手動設置值
好像這一點與上面說的很矛盾啊,其實這一條的重點是,不要爲多個枚舉項設置值,特別是在枚舉項特別多的情況下,因爲這很容易出錯,如下面代碼所示:
enum Color { Red, Orange = 2, Yellow, Green, Blue, Indigo = 5, Purple } console.log(Color.Blue !== Color.Indigo ? 'yes' : 'no'); // 'no'
很不幸的是,您原本認爲這兩個值不一樣,可結果是一樣的,這是由於您的疏忽而導致的(如果 TypeScript 能夠爲此提供審查的機制,並在編譯時報錯,我們就很大程度上避免犯這樣的錯)。我們可以繼續看一下編譯後的結果:
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]
獲取的值也具有歧義,程序的穩定性也因此受到影響,所以如果不是出於某種特殊目的,就不要手動去設置值了。
3. 不要爲枚舉項設置字符串數據類型
您可能會好奇了,難道除了數字類型,還能設置字符串類型?是的,的確可以。但是我爲什麼不推薦這麼做呢?我們看一下下面代碼編譯後的結果就知道了:
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 = {}));
不難發現,我們沒法像 Color[1]
這樣使用 Color['Blue Color']
了,這樣就導致行爲結果與預期的不一致性,其次,如果像下面這樣寫,TypeScript 還會報錯,因爲下面的值沒法根據上面的字符串值繼續遞增或推導了:
enum Color { Red, Green = 'Green Color', Blue // error: Enum member must have initializer. }
所以這裏強烈不建議將枚舉項的值設置爲字符串類型。
作者:Levid_GC
鏈接:https://www.jianshu.com/p/42241a597a50
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。