为何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)字符串、数字。并且它是类型安全的。

相关文章
相关标签/搜索