第十一节: TypeScript 枚举

使用枚举我们可以定义一些带名字的常量。TypeScript 支持数字的和基于字符串的枚举。

1. 枚举的理解

1.1 声明枚举

enum类型是对JavaScript标准数据类型的一个补充,像C#等其他语言一样,

使用枚举类型可以为一组数据赋予变量名称, 也可以直接为数据

使用关键字enum声明枚举

enum Color { 'pink', 'blue', 'skyblue' }
console.log(Color)

enum Color { pink, blue, skyblue }
console.log(Color)

上面两种写法的结果完全一样, 编译后的结果为Color值

/*
  { 
    '0': 'pink',
    '1': 'blue',
    '2': 'skyblue',
    pink: 0,
    blue: 1,
    skyblue: 2 
  }
*/


1.2 枚举操作

声明完枚举后, 使用枚举很简单:只需将任何成员作为枚举本身的属性访问,并使用枚举的名称声明类型:

例如:

// 声明枚举
// pink, skyblue, red 为一组命名常量
enum Color {pink, skyblue, red}

// 通过值查询常量(获取的常量是字符串)
let colorName:string = Color[2]

// 通过常量查询值
let colorPinkIndex = Color['pink']

// 或者(如果只符合标识符规范可以使用点操作方法)
let colorPinkIndex2 = Color.pink

console.log(colorName)
console.log(colorPinkIndex)
console.log(colorPinkIndex2)

/*
  结果:
  red
  0
  0
*/


// 使用枚举作为声明的类型
function example(color:Color){
  // color类型:(parameter) color: Color
  
  console.log('color', color)
  // color 0
}

example(Color.pink)


1.3 枚举值不可修改

枚举的值为只读属性,不可修改, 修改则报错

例如:

enum Color {red, blue,yellow,}
console.log(Color)
/*
  0: "red"
  1: "blue"
  2: "yellow"
  blue: 1
  red: 0
  yellow: 2
*/

Color.red = 6
// 报错:无法分配到 "red" ,因为它是只读属性。

Color[1] = 'green'
// 报错:类型“typeof Color”中的索引签名仅允许读取。


2. 数字枚举

2.1 不使用初始化器

默认情况下,在不适用初始化器的情况下, 枚举的属性的值是从0开始

例如:

// 枚举
enum Color {red, blue,yellow,}
console.log(Color)
/*
  0: "red"
  1: "blue"
  2: "yellow"
  blue: 1
  red: 0
  yellow: 2
*/

实例中声明的枚举,red的值为0, blue的值为1, yellow值为2

这种自动递增行为对于我们关心每个值与同一枚举中的其他值不同的情况很有用。


2.2 使用初始化器

也是用使用初始化器, 修改枚举的初始值.

例如:

enum Color {red = 2,blue,yellow,}
console.log(Color);

// 编译后
/*
    {
        2: "red"
        3: "blue"
        4: "yellow"
        blue: 3
        red: 2
        yellow: 4
    }
*/

打印后发现枚举的值从2开始了, 自动增加


注意修改了值的项会影响之后的属性的值,不会影响之前的

例如:

enum Color {red,blue = 5, yellow,}
console.log(Color);

// 编译后
/*
    {
        0: "red"
        5: "blue"
        6: "yellow"
        blue: 5
        red: 0
        yellow: 6
    }
*/

代码解释: red项没有使用初始化器,默认值从0, 此时blue使用初始化器,将值修改为5,后面的枚举项如果没有使用初始化器, 那默认值将会从5开始递增, 因此yellow的值为6


当然也可以手动的给每一项都使用初始化器赋值初始值

例如:

enum Color {
  red = 6,
  blue = 5,
  yellow = 2,
}
console.log(Color);
// 编译后
/*
    {
        2: "yellow"
        5: "blue"
        6: "red"
        blue: 5
        red: 6
        yellow: 2
    }
*/


枚举的值除了可以在定义枚举的时候设置,也可以使用已经定义的常量

例如:

const aa: number = 10
const bb: number = 20
const cc: number = 30

// 定义枚举
enum Num {one =aa ,two = bb, three = cc};
console.log(Num)

/*
 枚举打印结果
 {
    10: "one"
    20: "two"
    30: "three"
    one: 10
    three: 30
    two: 20
 }
*/


注意如果枚举的值是变量, 一旦定义完枚举后,即时修改变量的内容,枚举的值也不会发生改变

let aa: number = 10
let bb: number = 20
let cc: number = 30

// 定义枚举
enum Num {one =aa ,two = bb, three = cc};
// 第一次打印枚举
console.log('aa',aa)  // 10
console.log(Num)     //  {10: "one", 20: "two", 30: "three", one: 10, two: 20, three: 30}

// 修改变量后,打印枚举
aa = 50
console.log('aa',aa)  // 50
console.log('Num', Num) //  {10: "one", 20: "two", 30: "three", one: 10, two: 20, three: 30}

修改变量后,变量的值发生变化,但是枚举的内容并没有因为变量的变化而变化


2.3 枚举初始化器

没有初始化器的枚举要么需要放在第一位,要么必须在使用数字常量或其他常量枚举成员初始化的数字枚举之后。

换句话说,以下是不允许的:

function getSomeValue(){
  return 2
}


// 枚举
enum E {
  A = getSomeValue(),
  B
}
// B报错: 枚举成员必须具有初始化表达式。

代码解释: B项没有使用初始化, 要么放在A之前, 要不有没有初始化的A初始化为数字常量. 但这里是一个方法, 虽然返回的是数字


2.4 理解枚举的本质

枚举的本质就是将我们声明的常量名称 和值形成相互映射关系.

例如:可以使用对象来模拟枚举的处理, 枚举的特性就是返回一个对象,对象中有属性值对,也有值属性对.

例如:

// 声明变量
let Color;

// 自执行函数为变量添加属性
(function (Color) {
    Color[Color["pink"] = 0] = "pink";
    Color[Color["blue"] = 1] = "blue";
    Color[Color["skyblue"] = 2] = "skyblue";
})(Color || (Color = {}));

console.log(Color);
/*
  { 
    '0': 'pink',
    '1': 'blue',
    '2': 'skyblue',
    pink: 0,
    blue: 1,
    skyblue: 2 
  }
*/

自定义的枚举打印结果的结构和枚举返回的结构一致.

注意: 赋值语句的返回值就是值本身


3. 字符串枚举

字符串枚举是一个类似的概,。在字符串枚举中,每个成员都必须使用字符串文字或另一个字符串枚举成员进行常量初始化。

例如:

enum Color { one ='blue', two = "skyblue", three = "red"}
console.log('Color', Color)

/*
 打印的结果:
 {
    one: "blue"
    three: "red"
    two: "skyblue"
 }
*/

虽然字符串枚举没有自动递增的行为,但字符串枚举的好处是它们可以很好地“序列化”。

字符串枚举允许您在代码运行时提供有意义且可读的值,而与枚举成员本身的名称无关。


字符串枚举没有值跟常量之间的映射关系,因此字符串枚举只能通过常量获取值,不能通过值获取常量.

例如:

enum Color { one ='blue', two = "skyblue", three = "red"}

// 通过属性获取值
console.log(Color.one)   // blue
console.log(Color.two)   // skyblue
console.log(Color.three)  // red

// 通过值获取索引
console.log(Color.blue)  // 报错:Property 'blue' does not exist on type 'typeof Color'.
// 另一种写法
console.log(Color['blue']) // 不报错, 此时将blue识别为常量, 返回undefined

此时枚举和对象在使用上没有什么区别了


4. 异构枚举

从技术上讲,枚举可以与字符串和数字成员混合,但不清楚你为什么要这样做:

enum Color { one =5, two = "skyblue", three = "red"}
console.log('Color', Color)

/*
结果:
    {
        5: "one"
        one: 5
        three: "red"
        two: "skyblue"
    }
*/


5. 计算成员与常量成员

每个枚举成员都有一个与之关联的值,可以是常量计算值。在以下情况下,枚举成员被认为是常量:

5.1 成员第一项无初始化

枚举中的第一个成员,并且没有初始化程序,在这种情况下,它被分配了值0

// E.X is constant:
enum E {
    X,
}

此时X为常量值0


5.2 成员无初始化程序,并且前面的成员是数字常量

枚举当前项初始化程序,并且前面的枚举成员是一个数字常量。在这种情况下,当前枚举成员的值将是前一个枚举成员的值加一。

enum E1 {
  X,
  Y,
  Z,
}
 
enum E2 {
  A = 1,
  B,
  C,
}
  
// 此时 E1, E2 所有枚举成员都是 常量


5.3 枚举成员使用常量枚举表达式进行初始化

常量枚举表达式是可以在编译时完全评估的 TypeScript 表达式的子集。一个表达式是一个常量枚举表达式,如果它是:

  1. 文字枚举表达式(基本上是字符串或数字文字)
  2. 对向前定义的常量枚举成员的引用(源自于不同枚举)
  3. 带括号的常量枚举表达式
  4. 应用常量枚举表达式的+,-一元运算符之一的~
  5. +, -, *, /, %, <<, >>, >>>, &, |,^以常量枚举表达式作为操作数的二元运算符

将常量枚举表达式计算为NaNor是编译时错误Infinity

在所有其他情况下,枚举成员被认为是计算的。

例如:

enum FileAccess {
  // constant members
  None,
  Read = 1 << 1,
  Write = 1 << 2,
  ReadWrite = Read | Write,
  // computed member
  G = "123".length,
}
console.log(FileAccess)
/*
{
  0: "None"
  2: "Read"
  3: "G"
  4: "Write"
  6: "ReadWrite"
  G: 3
  None: 0
  Read: 2
  ReadWrite: 6
  Write: 4
}
*/

提示:

  • 计算结果必须为常量。
  • 计算项必须放在最后。


6. 联合枚举和枚举成员类型

有一个特殊的未计算的常量枚举成员子集:文字枚举成员。文字枚举成员是没有初始化值的常量枚举成员,或者具有初始化为的值

  1. 任何字符串文字(例如:'foo', 'bar','baz')
  2. 任何数字文字(1,10)
  3. 应用于任何数字文字的一元减号(-1,-10)

当枚举中的所有成员都具有文字枚举值时,一些特殊的语义就会发挥作用。

首先是枚举成员也变成了类型!

例如,我们可以说某些成员只能具有枚举成员的值:

// 枚举
enum ShapeKind {
  Circle,
  Square,
}
 
// 接口
interface Circle {
  kind: ShapeKind.Circle;
  radius: number;
}
 
// 接口
interface Square {
  kind: ShapeKind.Square;
  sideLength: number;
}
 
let c: Circle = {
  kind: ShapeKind.Square,
  // 不能将类型“ShapeKind.Square”分配给类型“ShapeKind.Circle”
  radius: 100,
};

代码解释:这里报错的原因只是类型不匹配,而ShapeKind.Square,ShapeKind.Circle其实都是数字number类型的子类型


另一个变化是枚举类型本身有效地成为每个枚举成员的联合。使用联合枚举,类型系统能够利用它知道枚举本身中存在的确切值集的事实。

正因为如此,TypeScript 可以捕获我们可能会错误地比较值。

例如:

// 枚举
enum E {
  Foo,Bar
}

// 函数
// E 类型其实就是 E.Foo | E.Bar 联合类型
function fn(x:E){
  if(x !== E.Foo || x !== E.Bar){
    // 报错:此条件将始终返回 "true",因为类型 "E.Foo" 和 "E.Bar" 没有重叠。
  }
}


7. 运行时枚举

枚举是运行时存在的真实对象

// 枚举
enum E {X,Y,Z}
 
// 函数参数是一个对象类型
function f(obj: { X: number }) {
  return obj.X;
}
 
f(E);

这段代码运行是成功, 因为枚举E中有一个X属性, 所以能匹配{X:number}对象类型.


8. 编译时枚举

尽管枚举是运行时存在的真实对象,但keyof关键字的工作方式与您对典型对象的预期不同。相反,用于keyof typeof获取将所有 Enum 键表示为字符串的类型。

enum LogLevel {ERROR,WARN,INFO,DEBUG,}
 
/**
 * z这相当于
 * type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
 */
// LogLevelStrings 是 文字类型的联合类型
type LogLevelStrings = keyof typeof LogLevel;
 
// 函数            
function printImportant(key: LogLevelStrings, message: string) {
  // 枚举中获取传入的项对应的值
  const num = LogLevel[key]; 
    
    // 判断这个值是否小于WARN项对应的值
  if (num <= LogLevel.WARN) {
    console.log("Log level key is:", key);
    console.log("Log level value is:", num);
    console.log("Log level message is:", message);
  }
}
printImportant("ERROR", "This is a message");


9. 反向映射

除了为成员创建具有属性名称的对象外,数字枚举成员还获得从枚举值到枚举名称的反向映射。例如,在此示例中:

 enum Color {Red,Blue , Yellow,}

 
let red = Enum.Red;
// 此时red的值为 0
// 等价于 Enum[0]
let nameOfA = Enum[red]; // "A"

TypeScript 将其编译为以下 JavaScript:

// 编译后的代码
"use strict";
var Color;
(function (Color) {
    Color[Color["Red"] = 0] = "Red";
    Color[Color["Blue"] = 1] = "Blue";
    Color[Color["Yellow"] = 2] = "Yellow";
})(Color || (Color = {}));

提示

  • 字符串枚举成员根本不会生成反向映射。
  • 枚举类型被编译成一个对象,它包含了正向映射( name -> value)和反向映射( value -> name)。


10. const枚举

可以使用const枚举。常量枚举是使用枚举上的const修饰符定义的:

// 常量枚举
const enum Color {Red,Blue , Yellow,}

const blue = Color.Blue

查看编译后的代码

'use strict'
const blue = 1 /* Blue */;

发现枚举类型编译后应该出现的对象没有了, 只剩下blue常量, 这就是使用const关键字声明枚举的作用

因为变量blue已经使用过枚举类型, 在编译阶段TypeScript就将枚举类型删除, 这也是性能提升的一种方案


11. 环境枚举

环境枚举用于描述已经存在的枚举类型的形状。通常使用在.d.ts文件中

// 环境枚举
declare enum Color {Red,Blue , Yellow,}

环境枚举和非环境枚举之间的一个重要区别是,在常规枚举中,如果之前的枚举成员被认为是常量,那么没有初始化器的成员将被认为是常量。相比之下,没有初始值设定项的环境(和非常量)枚举成员始终被视为已计算。


12. 枚举合并

同名的枚举和 自动合并, 但要注释分来的同名枚举项不会自动递增, 需要注意枚举值

例如:

enum Color {Red,Blue , Yellow}
enum Color {Pink,Green}
// 在包含多个声明的枚举中,只有一个声明可以省略其第一个枚举元素的初始化表达式

代码报错的原因在于,Red初始值为0, Pink的初始值就不能省略, 不然编译后代码如下

var Color;
(function (Color) {
    Color[Color["Red"] = 0] = "Red";
    Color[Color["Blue"] = 1] = "Blue";
    Color[Color["Yellow"] = 2] = "Yellow";
})(Color || (Color = {}));
(function (Color) {
    Color[Color["Pink"] = 0] = "Pink";
    Color[Color["Green"] = 1] = "Green";
})(Color || (Color = {}));

在使用RedPink时就会出问题, 所以TypeScript报错告诉你只能省略第一个枚举元素的初始化表达式


修改代码

enum Color {Red,Blue , Yellow}
enum Color {Pink = 5,Green}

编译后,枚举合并

var Color;
(function (Color) {
    Color[Color["Red"] = 0] = "Red";
    Color[Color["Blue"] = 1] = "Blue";
    Color[Color["Yellow"] = 2] = "Yellow";
})(Color || (Color = {}));
(function (Color) {
    Color[Color["Pink"] = 5] = "Pink";
    Color[Color["Green"] = 6] = "Green";
})(Color || (Color = {}));


小结:

  • 通过关键字 enum 来声明枚举类型。
  • TypeScript 仅支持基于数字和字符串的枚举。
  • 通过枚举类型编译后的结果,了解到其本质上就是 JavaScript 对象。

你可能感兴趣的:(第十一节: TypeScript 枚举)