爲何TypeScript的Enum頗有問題

原文地址在這裏java

TypeScript引入了不少靜態編譯語言的特性,好比class(如今是JavaScript的一部分了),interface, genericsunion types等。typescript

可是今天有一個類型須要着重討論下,這就是enum數據庫

對於不少的靜態語言來講,枚舉是一個很很是常見的語言特性。好比,c,c#,java和swift。枚舉就是你在代碼裏能夠用的一組常量。swift

咱們用TypeScript來新建一個enum來表明一週的幾天:c#

enum DayOfWeek {
  Sunday,
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday
};
複製代碼

這個枚舉使用enum關鍵字聲明,後面跟着DayOfWeek名稱。而後咱們定義枚舉裏可使用的常量。安全

如今咱們定義一個方法,接受這個枚舉類型的參數,來判斷傳入的參數是否是週末。markdown

function isItTheWeekend(day: DayOfWeek) {
  switch (day) {
    case DayOfWeek.Sunday:
    case DayOfWeek.Saturday:
      return true;
 
    default:
      return false;
  }
}
複製代碼

最後,咱們能夠這要用:oop

console.log(isItTheWeekend(DayOfWeek.Monday)); // log: false
複製代碼

對於消除程序裏的魔法字符串來講,這是一個很是有用的方法。ui

可是,事情遠不是咱們想的這麼簡單。下面的代碼調用會在TypeScript編譯以後獲得什麼呢?this

console.log(isItTheWeekend(2)); // is this valid?
複製代碼

知道結果你會嚇一跳。這樣的調用是符合TypeScript規則的,編譯器也會順利編譯。

發生了什麼呢?

上面的狀況可能會讓你認爲你發現了一個TypeScript的bug。其實TypeScript就是這麼設計的。

咱們這裏新建了一個數字枚舉,並且咱們能夠在TypeScript Playground裏看看編譯出來的結果是什麼:

var DayOfWeek;
(function (DayOfWeek) {
    DayOfWeek[DayOfWeek["Sunday"] = 0] = "Sunday";
    DayOfWeek[DayOfWeek["Monday"] = 1] = "Monday";
    DayOfWeek[DayOfWeek["Tuesday"] = 2] = "Tuesday";
    DayOfWeek[DayOfWeek["Wednesday"] = 3] = "Wednesday";
    DayOfWeek[DayOfWeek["Thursday"] = 4] = "Thursday";
    DayOfWeek[DayOfWeek["Friday"] = 5] = "Friday";
    DayOfWeek[DayOfWeek["Saturday"] = 6] = "Saturday";
})(DayOfWeek || (DayOfWeek = {}));
複製代碼

運行結果是:

image.png

事實上枚舉就是一個JavaScript對象。

這個對象的屬性就是根據我進定義的枚舉常量生成,還根據定義的順序生成了對應的數字(順序,Sunday是0,Saturday是6)。這個對象也有數字做爲key,對應的常量字符串做爲值的屬性。

所以,咱們能夠給上面的方法傳入數字,數字映射到對應的枚舉值。枚舉既是一個數字常量也是一個字符串常量。

何時用

若是一個方法接收一個枚舉類型參數,可是一個任意的數字就能夠經過編譯的話。這樣的結果顯然破壞了TypeScript構建的類型安全體系。這何時能夠用呢?

假設你有一個服務返回一個JSON串,你想把這個串建模,對應的某個屬性是一個枚舉。

在你的數據庫裏存的是數字。定義一個TypeScript枚舉能夠很容易解決這個問題:

const day: DayOfWeek = 3;
複製代碼

這個在賦值時執行的顯示的類型轉換會把數字轉換成枚舉的對應常量。也就是說咱們在代碼裏使用這個枚舉會讓代碼更容易讀懂。

控制枚舉的數字

枚舉的成員對應的數字是根據枚舉常量定義的順序生成的。那咱們是否能夠控制這個數字的值呢?是能夠的。

enum FileState {
  Read = 1,
  Write = 2
}
複製代碼

只是描述一個文件可能的狀態的枚舉。

它多是讀也多是寫狀態,咱們顯示的定義了枚舉值。如今就很明確什麼樣的值是合理的,由於顯示定義了。

Bit值

可是還有另外一個狀況頗有用,位值(Bit)。

咱們再來看一下這個FileState枚舉,給它添加一個新的枚舉值ReadWrite

enum FileState {
  Read = 1,
  Write = 2,
  ReadWrite = 3
}
複製代碼

以後假設有一個方法接受這個類型的參數:

const file = await getFile("/path/to/file", FileState.Read | FileState.Write);
複製代碼

咱們在FileState上使用了|操做符。這樣咱們可使用位運算來得到一個新的枚舉值。在這個例子裏面就是3ReadWrite的值。

事實上,咱們能夠寫的更清楚一些:

enum FileState {
  Read = 1,
  Write = 2,
  ReadWrite = Read | Write
}
複製代碼

這個ReadWrite的值不是寫死的,而是位運算獲得的。

可是再這樣使用枚舉的時候要多加當心。

以下的枚舉:

enum Foo {
  A = 1,
  B = 2,
  C = 3,
  D = 4,
  E = 5
}
複製代碼

若是要獲得E(或者5),能夠位運算獲得麼:Foo.A | Foo.D or Foo.B | Foo.C?

因此若是要用枚舉值作位運算,那麼明確如何獲得這個值。

控制索引

通常狀況下,每一個枚舉值都會有一個默認的數字值。若是須要也能夠明確的給這些枚舉值賦值。另外,還能夠給某部分枚舉賦值:

enum DayOfWeek {
  Sunday,
  Monday,
  Tuesday,
  Wednesday = 10,
  Thursday,
  Friday,
  Saturday
}
複製代碼

前幾個值是按照位置賦值,Sunday到TuesDay是0到2.以後在Wednesday給了一個新值,從這開始每一個值都遞增1. 這就可能會出現問題了:

enum DayOfWeek {
  Sunday,
  Monday,
  Tuesday,
  Wednesday = 10,
  Thursday = 2,
  Friday,
  Saturday
}
複製代碼

Tuesday賦值爲2,生成的JavaScript是什麼樣子呢:

var DayOfWeek;
(function (DayOfWeek) {
    DayOfWeek[DayOfWeek["Sunday"] = 0] = "Sunday";
    DayOfWeek[DayOfWeek["Monday"] = 1] = "Monday";
    DayOfWeek[DayOfWeek["Tuesday"] = 2] = "Tuesday";
    DayOfWeek[DayOfWeek["Wednesday"] = 10] = "Wednesday";
    DayOfWeek[DayOfWeek["Thursday"] = 2] = "Thursday";
    DayOfWeek[DayOfWeek["Friday"] = 3] = "Friday";
    DayOfWeek[DayOfWeek["Saturday"] = 4] = "Saturday";
})(DayOfWeek || (DayOfWeek = {}));
複製代碼

看起來Tuesday和Thursday的數值都是2。

因此,須要顯示的設定數值。

非數字枚舉

目前爲止,咱們只討論了數值枚舉,可是枚舉的值不必定非的是數字。它也能夠是任何常量或者計算值:

enum DayOfWeek {
  Sunday = "Sun",
  Monday = "Mon",
  Tuesday = "Tues",
  Wednesday = "Wed",
  Thursday = "Thurs",
  Friday = "Fri",
  Saturday = "Sat"
}
複製代碼

如今就不能給isItTheWeekend方法穿數字參數了。這個枚舉已經再也不是數字枚舉。然而,咱們也不能傳任意字符串進去,由於枚舉知道什麼樣的值纔是合理的。

這樣也帶來另一個問題:

const day: DayOfWeek = "Mon";
複製代碼

這樣是行不通的。

字符串並不能直接給枚舉賦值,而是須要一個顯示的類型轉換:

const day = "Mon" as DayOfWeek;
複製代碼

能不能給它賦其餘值呢?事實上枚舉能夠有不少類型的值:

enum Confusing {
  A,
  B = 1,
  C = 1 << 8,
  D = 1 + 2,
  E = "Hello World".length
}
複製代碼

這個例子的枚舉值都是數字。可是這些數字值能夠直接賦值,也能夠是計算值,或者是字符串的length屬性。若是都是常量的話,那麼就能夠是多種類型的值:

enum MoreConfusion {
  A,
  B = 2,
  C = "C"
}
複製代碼

這種狀況就很難讓人理解枚舉後面的數據是怎麼工做的。因此,最好不要用這樣的枚舉。

結論

TypeScript的枚舉是對JavaScript的一個很好地補充,使用得當將很是有用。它將有助於清理代碼中存在的魔術值(magic values)字符串、數字。並且它是類型安全的。

相關文章
相關標籤/搜索