TypeScript 基础 枚举 enum

发布于 2025-02-17 20:00:17 字数 7744 浏览 10 评论 0

TypeScript 支持 数字 的和基于 字符串枚举

使用枚举我们可以定义一些 带名字的常量 。 使用枚举可以清晰地表达意图或创建一组有区别的用例。

数字枚举

// Up 使用初始化为 1。 其余的成员会从 1 开始自动增长。 换句话说,Direction.Up 的值为 1,Down 为 2,Left 为 3,Right 为 4。
enum Direction {
    Up = 1,
    Down,
    Left,
    Right
}

// 还可以完全不使用初始化器
// 现在,Up 的值为 0,Down 的值为 1 等等
enum Direction {
    Up,
    Down,
    Left,
    Right,
}

使用方式

通过 枚举的属性 来访问 枚举成员

通过 枚举的名字 来访问 枚举类型

enum Response {
    No = 0,
    Yes = 1,
}
// 解析为 JS
var Response;
(function (Response) { # 解析为下面这样的代码的原因是让数字枚举具有反向映射的作用:根据值得到名
    Response[Response["No"] = 0] = "No";
    Response[Response["Yes"] = 1] = "Yes";
})(Response || (Response = {}));


function respond(recipient: string, message: Response): void {
    // ...
}

respond("Princess Caroline", Response.Yes)

计算的和常量成员

每个枚举成员都带有一个值,它可以是 常量 计算出来的

当满足如下条件时,枚举成员被当作是 常量

  1. 它是枚举的第一个成员且没有初始化器,这种情况下它被赋予值 0
  2. 它不带有初始化器且它之前的枚举成员是一个 数字 常量。 这种情况下,当前枚举成员的值为它上一个枚举成员的值加 1
  3. 枚举成员使用 常量枚举表达式 初始化。 常数枚举表达式是 TypeScript 表达式的子集,它可以 在编译阶段求值 。 当一个表达式满足下面条件之一时,它就是一个常量枚举表达式:
    • 一个枚举表达式字面量(主要是 字符串字面量数字字面量
    • 一个对之前定义的 常量枚举成员的引用 (可以是在不同的枚举类型中定义的)
    • 带括号的常量枚举表达式
    • 一元运算符 + , - , ~ 其中之一应用在了常量枚举表达式
    • 常量枚举表达式做为 二元运算符 + , - , * , / , % , << (乘), >> (除), >>>(无符号右移) , & , | , ^ 的操作对象 。 若常数枚举表达式求值后为 NaNInfinity ,则会在编译阶段报错。

      << 无论正负,低位补 0, >> 正则高位补 0,负则高位补 1, >>> 无论正负,高位补 0

所有其它情况的枚举成员被当作是需要计算得出的值。

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

字符串枚举

在一个字符串枚举里,每个成员都必须 用字符串字面量 ,或另外 一个字符串枚举成员 进行初始化。

enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}
//解析为 JS
var Direction;
(function (Direction) { # 不会为字符串枚举成员生成反向映射
    Direction["Up"] = "UP";
    Direction["Down"] = "DOWN";
    Direction["Left"] = "LEFT";
    Direction["Right"] = "RIGHT";
})(Direction || (Direction = {}));

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

反向映射

数字枚举成员还具有了 反向映射 ,从枚举值到枚举名字。

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 )。 引用枚举成员总会生成为对属性访问并且永远也不会内联代码。

异构枚举(Heterogeneous enums)

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

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

除非你真的想要利用 JavaScript 运行时 的行为,否则我们不建议这样做。

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

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

  • 任何字符串字面量(例如: "foo""bar""baz"
  • 任何数字字面量(例如: 1 , 100
  • 应用了一元 - 符号的数字字面量(例如: -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,
    //    ~~~~~~~~~~~~~~~~ Error!
    radius: 100,
}

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

enum E {
    Foo,
    Bar,
}

function f(x: E) {
    if (x !== E.Foo || x !== E.Bar) {
        //             ~~~~~~~~~~~ 此条件将始终返回“true”,因为类型“E.Foo”和“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
}
// 实际上可以传递给函数
function f(obj: { X: number }) {
    return obj.X;
}

// 有效,因为“E”有一个名为“X”的属性,它是一个数字。
f(E);

编译时的枚举

尽管一个枚举是在运行时真正存在的对象,但 keyof 关键字的行为与其作用在对象上时有所不同。应该使用 keyof typeof 来获取一个表示枚举里所有字符串 key 的类型。

enum LogLevel {
    ERROR, WARN, INFO, DEBUG
}

/**
 * 等同于:
 * type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
 */
type LogLevelStrings = keyof typeof LogLevel;

function printImportant(key: LogLevelStrings, message: string) {
    const num = LogLevel[key];
    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');

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
}

外部枚举 和 非外部枚举 之间有一个重要的区别 :

  • 在正常的枚举里,没有初始化方法的成员被当成常数成员。
  • 对于非常数的外部枚举而言,没有初始化方法时被当做需要经过计算的。

小结

  1. TypeScript 支持 数字 的和基于 字符串枚举
  2. 字符串枚举没有自增长的行为,字符串枚举可以很好的序列化
  3. 数字枚举成员还具有了 反向映射 ,从枚举值到枚举名字。但 不会 为字符串枚举成员生成反向映射
  4. const 常量枚举在编译为 JS 的阶段会被删除,而常量枚举成员在使用的地方会被内联进来,这是因为常量枚举不允许包含计算成员
  5. 外部枚举用来描述已经存在的枚举类型的形状,外部枚举在编译阶段也会被删除

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

厌味

暂无简介

文章
评论
26 人气
更多

推荐作者

佚名

文章 0 评论 0

今天

文章 0 评论 0

゛时过境迁

文章 0 评论 0

达拉崩吧

文章 0 评论 0

呆萌少年

文章 0 评论 0

孤者何惧

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文