【TypeScript】深入学习TypeScript枚举

TypeScript学习:TypeScript从入门到精通

蓝桥杯真题解析:蓝桥杯Web国赛真题解析

个人简介:即将大三的学生,热爱前端,热爱生活
你的一键三连是我更新的最大动力❤️!


前言

最近博主一直在创作TypeScript的内容,所有的TypeScript文章都在我的TypeScript从入门到精通专栏里,每一篇文章都是精心打磨的优质好文,并且非常的全面和细致,期待你的订阅❤️

本篇文章将深入去讲解TypeScript中的枚举,这也许会是你看过的最全面最细致的TypeScript教程,点赞关注收藏不迷路!

在【TypeScript】TypeScript数据类型(下篇)中,我们了解到枚举是 TypeScript 添加到 JavaScript 的一项功能,它允许描述一个值,该值可能是一组可能的命名常量之一,接下来让我们深入去学习枚举:

文章目录

  • 前言
  • 1、数字型枚举
    • 常量成员
    • 计算成员
    • 成员顺序
  • 2、字符串枚举
  • 3、异构枚举
  • 4、联合枚举和枚举成员类型
  • 5、运行时的枚举
  • 6、编译时的枚举
    • 反向映射
    • 常量枚举
  • 7、环境枚举
  • 8、对象与枚举
  • 结语

1、数字型枚举

一个枚举可以用 enum 关键字来定义:

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

上面就是一个数字枚举,其中 Up 被初始化为 1 ,所有下面的成员从这一点开始自动递增,即 Up 的值是 1 , Down 是 2 , Left 是3 , Right 是4

如果我们不使用初始化器,即不对Up赋值,则Up的值默认是0,之后的成员依旧开始递增

枚举的使用方式与对象类似:

enum Gender {
    male,
    female,
}

console.log(Gender.male, Gender.female); // 0 1

枚举可用作类型: 数字型枚举用作类型时能匹配任何数字,即想当于number类型

// 对于数字型枚举,这个a变量只能保存male和female,或任何数字
let a: Gender;
a = Gender.male;
a = Gender.female;
a = 99;
// a = "Ailjx"; // err :不能将类型“"Ailjx"”分配给类型“Gender”。

常量成员

每个枚举成员都有一个与之关联的值,可以是常量计算值

在以下情况下,枚举成员被认为是常量

  • 没有初始化器(未初始化值)

    如果是第一个成员就自动被赋值为0,如果不是第一个成员就自动被赋值为上一个值加1,无论如何都有一个确定的值,所以被认为是常量

    // E1,E2,E3中的所有枚举成员都是常数
    enum E1 { X, Y, Z,}
    enum E2 { A = 1, B, C,}
    enum E3 {D,}
    
  • 枚举成员用以下常量枚举表达式进行初始化:

    1. 枚举表达式的字面意思(基本上是一个字符串字面量或一个数字字面量)

      enum E1 { x = "1"}
      enum E2 { x = 1 }
      
    2. 对先前定义的常量枚举成员的引用(可以来自不同的枚举)

      enum E1 {x,}
      enum E2 {x = E1.x, y = E2.x,}
      
    3. 一个括号内的常量枚举表达式

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

      enum E { x = -9,}
      
    5. + , - , * , / , % , << , >> , >> , & , | , ^ 以常量枚举表达式为操作数的二元运算符

      enum E1 {x = 1,}
      enum E2 {x = E1.x + 1,}
      

    如果常量枚举表达式被评估为NaNInfinity ,这是一个编译时错误

计算成员

除了常量成员之外的就都是计算成员:

let a = 1;
function fn() {
    return 1;
}
enum E {
    x = ++a,
    y = a,
    z = fn(),
    A = "Ailjx".length,
}

枚举E中的所有成员都是计算成员

成员顺序

数字枚举可以混合在计算成员和常量成员中,但没有初始化器的枚举成员要么需要放在第一位,要么必须在常量成员之后(因为只有在常量成员之后才会自增1):

let a = 1;
enum E {
    x,
    y = a,
}

这个例子中y是计算成员,则x只能放在y的前边即第一位,不然会报错:

【TypeScript】深入学习TypeScript枚举_第1张图片

因为x没有初始化器,若它不在第一位,它就会在上一个成员的基础上加1,但若上一个成员是计算成员,这种行为就不会被TypeScript处理了,并会抛出错误

2、字符串枚举

在一个字符串枚举中,每个成员都必须用一个字符串或另一个字符串枚举成员进行常量初始化:

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

字符串枚举没有自动递增的行为

字符串枚举用作类型时只能匹配到自身枚举成员:

enum Gender {
    male = "9",
    female = "Ailjx",
}

// 这个a变量只能保存male和female,数字和字符串都不行
let a: Gender;
a = Gender.male;
a = Gender.female;
// a = 9; // 不能将类型“9”分配给类型“Gender”
// a = "Ailjx"; // err :不能将类型“"Ailjx"”分配给类型“Gender”。

3、异构枚举

字符串和数字成员混合的枚举称为异构枚举,但官方并不建议这么做:

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

4、联合枚举和枚举成员类型

字面枚举成员是一个没有初始化值的常量枚举成员,或者其值被初始化为:

  • 任何字符串(例如: "foo" , "bar" , "baz" )。
  • 任何数字字头(例如: 1 , 100 )
  • 应用于任何数字字面的单数减号(例如: -1 , -100 )

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

  • 枚举成员也能当作类型来用

    enum E {
        A = 1,
        B = "Ailjx",
        C = "Ailjx",
        D = -1,
    }
    
    interface Author {
        // E.A和E.D相当于number类型
        age: E.A;
        age2: E.D;
        // 而E.B可不是简单的string类型,它限制了只有枚举E中值为"Ailjx"的成员才能赋值给name和name2
        name: E.B;
        name2: E.B;
    }
    
    let c: Author = {
        age: 12, // ok
        age2: 36, // ok
        name: E.C, // ok
        // name2的类型为E.B,并非简单的是"Ailjx"字面类型,只有枚举E中的"Ailjx"才能对其赋值
        name2: "Ailjx", // err:不能将类型“"Ailjx"”分配给类型“E.B”
    };
    
  • 枚举类型本身有效地成为每个枚举成员的联合,使用联合枚举,类型系统能够利用它知道枚举本身中存在的确切值集的事实,正因为如此,TypeScript 可以捕获我们可能会错误地比较值的错误:

    enum E {
        Foo,
        Bar,
    }
    function f(x: E) {
        if (x !== E.Foo || x !== E.Bar) {
            // ❌❌❌err:此条件将始终返回 "true",因为类型 "E.Foo" 和 "E.Bar" 没有重叠。
            //...
        }
    }
    

5、运行时的枚举

枚举是在运行时存在的真实对象,例如,下面这个枚举:

enum E {
    X,
    Y,
    Z,
}

实际上可以传递给函数:

enum E {
    X,
    Y,
    Z,
}
function f(obj: { X: number }) {
    return obj.X;
}
// 可以正常工作,因为'E'有一个名为'X'的属性,是一个数字。
f(E);

6、编译时的枚举

尽管枚举是运行时存在的真实对象,但keyof关键字对枚举的工作方式与对典型对象的预期完全不同:

// 对象类型
interface A {
    b: number;
    c: number;
}
// type T = "b"|"c"
type T = keyof A;
let a: T = "b";
a = "c";
// 枚举类型
enum E {
    X,
    Y,
    Z,
}
// type T = "toString" | "toFixed" | "toExponential" | "toPrecision" | "valueOf" | "toLocaleString"
type T = keyof E;

从上可以看到,我们不能使用keyof来获取枚举类型键的字面联合类型

可以使用keyof typeof 来获得一个将枚举类型所有键表示为字符串的类型:

// 枚举类型
enum E {
    X,
    Y,
    Z,
}
// type T = "X" | "Y" | "Z"
type T = keyof typeof E;

从这,我们反向思考能发现对枚举类型使用typeof能够获得该枚举的对象类型:

// 枚举类型
enum E {
    X,
    Y,
    Z,
}

// type T = { X: number, Y: number, Z: number }
type T = typeof E;
const a: T = { X: 1, Y: 2, Z: 3 };

反向映射

数字枚举的成员还可以得到从枚举值到枚举名称的反向映射:

enum Enum {
    A,
}
let a = Enum.A; // a为枚举值
console.log(a); // 0
let nameOfA = Enum[a]; // 根据枚举值获得枚举名称(根据键值获得键名)
console.log(nameOfA); // "A"

TypeScript 将其编译为以下 JavaScript

"use strict";
var Enum;
(function (Enum) {
    Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
let a = Enum.A; // a为枚举值
console.log(a); // 0
let nameOfA = Enum[a]; // 根据枚举值获得枚举名称(根据键值获得键名)
console.log(nameOfA); // "A"

在此生成的代码中,枚举被编译成一个对象,该对象存储正向 ( name-> value) 和反向 ( value-> name) 映射,对其他枚举成员的引用始终作为属性访问发出,并且从不内联

字符串枚举成员不会被生成反向映射!

常量枚举

为了避免在访问枚举值时编译产生额外的生成代码和额外的间接性的代价,可以使用常量枚举,常量枚举使用枚举上的const 修饰符来定义的:

const enum E {
    A,
}

常量枚举只能使用常量枚举表达式( 常量枚举不能有计算成员),并且与常规枚举不同,它们在编译期间会被完全删除

const enum E {
    A,
}
let arr = E.A;

编译后:

"use strict";
let arr = 0 /* E.A */;

普通的枚举(去掉const修饰符)编译为:

"use strict";
var E;
(function (E) {
    E[E["A"] = 0] = "A";
})(E || (E = {}));
let arr = E.A;

7、环境枚举

使用declare来定义环境枚举,环境枚举成员初始化表达式必须是常数表达式(不能有计算成员):

【TypeScript】深入学习TypeScript枚举_第2张图片

8、对象与枚举

在现代TypeScript中,一般不需要使用枚举,因为一个对象的常量就足够了

const enum EDirection {
    Up,
    Down,
    Left,
    Right,
}
// (enum member) EDirection.Up = 0
EDirection.Up;

// 将枚举作为一个参数
// dir的类似于number类似
function walk(dir: EDirection) {}
walk(EDirection.Left);
walk(99) // ok

将上述代码改写成对象形式:

const ODirection = {
    Up: 0,
    Down: 1,
    Left: 2,
    Right: 3,
} as const;

// (property) Up: 0
ODirection.Up;

// 相比使用枚举,需要一个额外的行来推算出类型
type Direction = typeof ODirection[keyof typeof ODirection];
function run(dir: Direction) {} // type dir = 0 | 1 | 2 | 3

run(ODirection.Right);
run(99); // err:类型“99”的参数不能赋给类型“Direction”的参数

可以看到使用对象改写枚举反而会更安全,类型限制更准确

结语

至此,TypeScript枚举的内容就全部结束了,关注博主下篇更精彩!

博主的TypeScript从入门到精通专栏正在慢慢的补充之中,赶快关注订阅,与博主一起进步吧!期待你的三连支持。

参考资料:TypeScript官网

如果本篇文章对你有所帮助,还请客官一件四连!❤️

你可能感兴趣的:(typescript,学习,javascript)