TS学习第六节(枚举)

191 阅读7分钟

 学习地址

使用枚举我们可以定义一些带名字的常量。使用枚举可以清晰表达意图或创建一组有区别的用例。TypeScript支持数字的和基于字符串的枚举。

数字枚举

首先我们看看数字枚举:

enum Direction {
    Up = 1,
    Down,
    Left,
    Right
}

如上,我们定义了一个数字枚举,Up使用初始化为1,其余成员会从1开始自动增长。换句话说,Direction.Up的值为1,Down的值为2, Left的值为3,Right的值为4.

我们也可以不用初始化:

enum Direction{
    Up,
    Down,
    Left,
    Right
}

现在,Up的值为0,Down的值为1,等等。当我们不在乎成员的值的时候,这种自增长的行为是很有用处的,但是要注意每个枚举成员的值都是不同的。

使用枚举很简单:通过枚举的属性来访问枚举成员,和枚举的名字来访问枚举类型:

enum Response{
    No = 0,
    Yes = 1
}
function respond(recipient: string, message: Response): void{
    //...
}
respond("Princess Caroline", Respones.Yes)

数字枚举可以被混入到计算机和常量成员。简短地说,不带初始化器的枚举或者被放在第一的位置,或者被放在使用了数字常量或其它初始化了的枚举后面。换句话说,下面是不被允许的:

enum E{
    A: getSomeValue(),
    B, //error! 'A' is not constant-initialized, so 'B' needs an initializer
}

字符串枚举

字符串枚举的概念很简单,但是有细微的运行时的差别。在一个字符串枚举里,每个成员必须用字符串字面量,或者另外一个字符串枚举成员进行初始化。

enum Direction{
    Up = 'UP',
    Down = 'DOWN',
    Left = 'LEFT',
    Right = 'RIGHT'
}

由于字符串枚举没有自增长的行为,字符串可以很好的序列化。话句话说,如果你正在调试并且必须要读一个数字枚举运行时的值,这个值通常是很难读的,它并不能表达有用的信息(尽管反向映射会有所帮助),字符串枚举允许你提供运行时有意义的并且可读的值,独立于枚举成名的名字。

异构枚举

从技术角度来说,枚举可以混合字符串和数字成员,但是似乎你并不会这么做:

enum BooleanLikeHeterogeneousEnum{
    No = 0,
    Yes = 'YES'
}

除非是真的想利用JavaScript运行时的行为,否则不建议这么做。

计算的常量成员

每个枚举成员都带有一个值,它可以是常量或计算出来的。当满足如下条件时,枚举成员被当成常量:

1.它是枚举的第一个成员且没有初始化器,这种情况下它被赋值为0:

//E.x is constant:
enum E{ x }

2.它不带初始化器且它之前的枚举成员是一个数字常量。这种情况下,当前枚举成员的值为它上一个枚举成员的值加1.

//All enum members in 'E1' and 'E2' are constant.
enum E1{ X, Y, Z }

enum E2{
    A = 1, B, C
}

枚举成员使用常量枚举表达式初始化。常数枚举表达式是TypeScript表达式的子集,它可以在编译阶段求值。当一个表达式满足下面条件之一时,它就是一个常量枚举表达式:

        1.一个枚举表达式字面量(主要是字符串字面量或数字字面量)

        2.一个对之前定义的常量枚举成员的引用(可以是在不同的枚举类型中定义的)

        3.带括号的常量枚举表达式

        4.一元运算符 +, - ~其中之一应用在于常量枚举表达式

        5.常量枚举表达式作为二元运算符 +, -, * ,/, %, <<, >>, &, |, ^的操作对象,若常数                    枚举表达式求值后为NaN或Infinity,则会在编译阶段报错。

所有其他情况的枚举成员被当做是需要计算出来的值。

enum FileAccess{
    // constant members
    None,
    Read = 1 << 1,
    Write = 1 <<2,
    ReadWrite = Read | Write,
    //computed member
    G = '123'.length
}

联合枚举与枚举成员的类型

存在一种特殊的非计算的常量枚举成员的子集:字面量枚举成员。字面量枚举成员是指不带初始值的常量枚举成员,或者是值被初始化

          1.任何字符串字面量(例如:"foo", "bar", "baz")

           2.任何数字字面量(例如:1, 100)

          3.应用了一元 - 符号的数字字面量(例如:-1,-100)

当所有枚举成员都拥有字面量枚举值时,它就带有了一种特殊的语义。

首先,枚举成员称为了类型!例如,我们可以说某些成员只能是枚举成员的值:

enum ShapeKind{
    Circle,
    Square,
}
interface Circle{
    kind: ShapeKind.Circle;
    radius: number;
}
interface Square{
    kind: ShapeKind.Square;
    sideLength: number;
}
let c: Circle = {
    kind: ShapeKind.Square,
    radius: 100
}

另一个变化是枚举类型本身变成了每个枚举成员的联合。虽然我们还没有讨论联合类型。但是只要知道通过联合枚举,类型系统能够利用这样一个事实,它就可以知道枚举里的值的集合。因此,TypeScript能够捕获在比较值的时候犯的愚蠢的错误。例如:

enum E{
    Foo,
    Bar,
}
function f(x: E){
    if(x !== E.Foo || x !== E.Bar){
        //
        //Error! Operator '!==' cannot be applied to types 'E.Foo' and 'E.Bar'
    }
}

这个例子里,我们先检查x是否不是E.Foo。如果通过了这个检查,然后 | | 会发生短路效果,if语句体内的内容会被执行。然而,这个检查没有通过,那么x则只能为 E.Foo,因此没理由再去检查它是否为E.Bar。

运行时的枚举

枚举是在运行时存在的对象。例如下面的枚举:

enum E{
    X, Y, Z
}

//can actually be passed around to functions
function f(obj: { X: number}){
    return obj.X;
}
//WOrks since 'E' has a property named 'X' which is a number.
f(E);

反向映射

除了创建一个属性名作为对象成员的对象之外,数字枚举成员还具有了反向映射,从枚举到枚举名字。例如,在下面的例子中:

enum Enum{
    A
}
let a = Enum.A;
let nameOfA = Enum{a}; //'A'

TypeScript可能会将这段代码编译为下面的JavaScript:

var Enum;
(function(Enum){
    Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
var a = Enum.A;
var nameOfA = Enum[a]; //'A'

生成的代码中,枚举类型编译成一个对象,它包含了正向映射( name --> value )和反向映射( value --> name)。引用枚举成员总会生成为对象访问并且拥有不会内联代码。

要注意的是不会为字符串枚举成员生成反向映射。

const枚举

大多数情况下,枚举是十分有效的方案。然后在某些情况下需要很严格。为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用const枚举。常量枚举通过在枚举上使用const修饰符来定义。

const enum Enum{
    A = 1,
    B = A * 2
}

常量枚举只能使用常量枚举表达式,并且不同于常规的枚举,它们在编译阶段会被删除。常量枚举成员在使用的地方会被内联进来。之所以可以这么做是因为,常量枚举不允许包含计算成员。

const enum Directions{
    Up,
    Down,
    Left,
    Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]

生成后的代码为:

var directions = [0/* Up */, 1/* Down */, 2/* Left */, 3/*Right*/];

外部枚举

外部枚举用来秒速已经存在的枚举类型的形状

declare enum Enum{
    A = 1,
    B,
    C = 2
}

外部枚举和非外部枚举之间有一个重要的区别,在正常的枚举里,没有初始化方法的成员被当成常数成员。对于非常数的外部枚举而言,没有初始化方法当做需要经过计算的。