TypeScript 數據類型——枚舉 (Enum)

如 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 數據類型。現在,我們就以上面提到的枚舉的特性一步步分析:

  1. 我們可以通過 . 號訪問枚舉的內容,這與訪問對象屬性的方式一樣,那麼我們是否可以將其認爲一個對象,這樣就可以得到以下的結構:
var Color = {
  Red: 0,
  Green: 1,
  Blue: 2
};
  1. 我們也可以通過具體的數值獲取其對象的名稱,這種操作和數組非常相似,傳遞索引值,提取具體索引位的值,這樣順理成章得出以下的結構:
var Color = ['Red', 'Green', 'Blue'];
  1. 現在將上面提到的兩個想法合併起來,那麼現在很多人可能傻眼了,#1 推導出來的是對象,#2 推導出來的是數組,這兩者怎麼結合起來?不要着急,想一想,數組可是一種特殊的對象,我們將推導寫在下面的代碼中:
// 因爲數組也是一種對象,那麼全部都用對象表示
var Color = {};

// 換一種方式改寫 #1 中的對象
Color['Red'] = 0;
Color['Green'] = 1;
Color['Blue'] = 2;

// 換一種方式改寫 #2 中的數組
Color[0] = 'Red';
Color[1] = 'Green';
Color[2] = 'Blue';
  1. 看完上面的代碼,是否感覺豁然開朗,現在我們繼續將上面的代碼精簡一下:
var Color = {};
Color[Color['Red'] = 0] = 'Red';
Color[Color['Green'] = 1] = 'Green';
Color[Color['Blue'] = 2] = 'Blue';
  1. 現在我們看一下,如果是 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
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章