跳至主要內容

枚举

樱桃茶大约 17 分钟

枚举

枚举(Enums)是 TypeScript 提供的一个功能,它可以帮助你以更易于理解和维护的方式处理一组相关的常数值。在 JavaScript 中,你可能会使用对象或者一系列的变量来实现这样的功能,但是这并不是很方便且容易出错。TypeScript 的枚举类型让你可以将这些值聚集在一起,用一种清晰且可读性高的方式来表达它们。

  • 基础枚举:你可以通过enum关键字来定义一个枚举。

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

    这里定义了一个名为Direction的枚举,包含四个成员:UpDownLeftRight。TypeScript 默认为枚举的成员赋值从 0 开始的数字,但你也可以手动指定成员的数值。

  • 字符串枚举:与数字枚举相比,字符串枚举允许你使用字符串而不是数字作为成员的值。

    enum FileAccess {
      Read = "READ",
      Write = "WRITE",
      Execute = "EXECUTE",
    }
    

    字符串枚举有助于提高代码的可读性和可维护性。

  • 计算的和常量成员:枚举可以包含常量成员(在编译时计算的)和计算成员(在运行时计算的)。

    • 常量成员的例子:
      enum FileAccess {
        None,
        Read = 1 << 1,
        Write = 1 << 2,
        ReadWrite = Read | Write,
        // 计算成员
        G = "123".length,
      }
      
      在上面的代码中,ReadWrite是通过ReadWrite计算得到的一个常量成员,而G是一个计算成员,因为它的值是在运行时计算得到的。
  • 枚举成员作为类型:使用枚举成员作为类型让你能够确保某个变量只能被赋予枚举中的某个成员作为其值。

    enum Direction {
      Up,
      Down,
      Left,
      Right,
    }
    
    let a: Direction = Direction.Up;
    

    这里变量a的类型被指定为Direction,这意味着它只能接受Direction枚举中的任一成员作为值。

  • 反向映射:对于数字枚举,TypeScript 会生成一个从枚举值到枚举名字的反向映射。

    enum Enum {
      A,
    }
    let a = Enum.A;
    let nameOfA = Enum[a]; // "A"
    

    这里,我们可以通过枚举值(如Enum.A的值)来获取其名称(即"A")。

通过使用枚举,你可以使代码更具有表达力,错误更少,易于维护。尤其是在处理一组固定的选项时,如月份、方向、用户角色等,枚举能够提供一种非常有效的解决方案。

枚举

枚举(Enums)是 TypeScript 提供的一种特性,让代码更具可读性和易于维护。它们用于定义命名的常量集合。

  • 基础枚举: TypeScript 支持数字和字符串枚举。

    • 数字枚举:

      • 默认情况下,枚举的成员会被赋值为从 0 开始递增的数字。
      • 可以手动指定成员的数值。
      enum Direction {
        Up, // 0
        Down, // 1
        Left, // 2
        Right, // 3
      }
      
      • 如果前一个成员有手动赋值,则后续成员递增。
      enum Direction {
        Up = 1,
        Down, // 2
        Left, // 3
        Right, // 4
      }
      
    • 字符串枚举:

      • 每个成员必须用字符串字面量或另一个字符串枚举成员进行初始化。
      enum Direction {
        Up = "UP",
        Down = "DOWN",
        Left = "LEFT",
        Right = "RIGHT",
      }
      
  • 联合枚举与枚举成员的类型:

    • 枚举成员成为了类型!你可以用枚举成员作为类型来使用。
    enum FileAccess {
      None,
      Read = 1 << 1,
      Write = 1 << 2,
      ReadWrite = Read | Write,
    }
    
    function controlAccess(file: FileAccess) {
      // ...
    }
    
  • 常量成员和计算成员:

    • 枚举分为常量成员(在编译时计算出结果)和计算成员(在程序执行时计算出结果)。
    • 如果枚举成员是常量,则可以在其他枚举成员中使用它们做为默认值。
    • 计算成员后面不可以跟随未初始化的成员。
    enum E {
      A = getSomeValue(),
      B,
    } // Error, because B is uninitialized and follows computed member A.
    
  • 枚举作为类型:

    • 你可以将枚举作为函数参数的类型,或者声明变量的类型。
    enum Color {
      Red,
      Green,
      Blue,
    }
    let c: Color = Color.Green;
    
  • 运行时的枚举:

    • 枚举是在运行时真实存在的对象。
    • 可以在运行时遍历枚举的值,或者反查键名。
    enum E {
      X,
      Y,
      Z,
    }
    function f(obj: { X: number }) {
      return obj.X;
    }
    f(E); // Works, since 'E' has a property named 'X' which is a number.
    
  • const 枚举:

    • 使用const关键字定义的枚举会在编译阶段被删除,并且不能包含计算成员。
    • const枚举的好处是在使用枚举值时,不会产生额外的代码开销。
    const enum Enum {
      A = 1,
      B = A * 2,
    }
    

数字枚举

数字枚举是 TypeScript 中的一种特性,允许你为一组数值赋予更易读的名字。在 JavaScript 中没有原生枚举类型,但 TypeScript 提供了这个概念来帮助你编写更清晰和可维护的代码。

  • 定义数字枚举: 数字枚举使用enum关键字定义,成员默认从0开始自增。

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

    在这个例子中,Direction.Up的值将会是0Direction.Down将会是1,以此类推。

  • 手动赋值: 你也可以手动指定枚举成员的数值。

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

    这里Direction.Up的值是1,其他成员会依次递增,所以Direction.Down2等等。

  • 使用枚举成员: 枚举成员可以作为类型使用,也可以作为值使用。

    function respond(recipient: string, message: Direction): void {
      // ...
    }
    
    respond("Princess Caroline", Direction.Up);
    

    这个函数期望第二个参数是Direction类型的成员。

  • 枚举反向映射: TypeScript 的数字枚举有一个特点是可以从枚举值反向映射到枚举名。

    enum Direction {
      Up = 1,
      Down,
      Left,
      Right,
    }
    
    let valueName = Direction[1]; // 输出:"Up"
    

    可以通过枚举值来访问它的名称,这里通过Direction[1]得到了名称"Up"

  • 不建议修改枚举成员的值: 一旦定义了数字枚举,在程序运行时应该避免改变它们的值。如果必须对枚举进行修改,则应当同时更新所有使用了该枚举的地方。

数字枚举提供了一种类型安全的方式来处理数字集合,使得代码在可读性和可维护性方面都有所提升。在 JavaScript 中虽然可以用对象或者其他结构来模拟枚举,但 TypeScript 的数字枚举使得管理这样的值集合变得更简单,更直观。

字符串枚举

字符串枚举(String Enums)是 TypeScript 提供的一种枚举类型,它允许你为枚举成员指定字符串值。

  • 在 JavaScript 中,并没有内置的枚举类型,但 TypeScript 通过字符串枚举使得代码更加清晰和易于理解。

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

实例说明字符串枚举的使用:

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

在这个例子中,我们定义了一个名为Direction的字符串枚举,它具有四个成员:UpDownLeftRight。每个成员都被初始化为一个特定的字符串。

使用字符串枚举的好处包括:

  • 可读性:字符串枚举的值通常是自描述的,这可以使得代码更容易被人阅读和理解。

  • 调试友好:当你打印或检查枚举成员时,会显示有意义的字符串而不是数字,这有助于调试过程。

  • 不变性:与数字枚举相比,字符串枚举的值在编译后不能改变,这为枚举值带来了额外的保障。

字符串枚举的限制:

  • 字符串枚举不支持反向映射。即你不能由枚举的值访问到枚举的名字,这与数字枚举的行为不同。

举例展示如何使用字符串枚举:

function respond(message: string, direction: Direction) {
  // ...
}

respond("What is your position?", Direction.Up);

在这个函数调用中,Direction.Up将传递字符串"UP"respond函数的direction参数。因为字符串枚举的值是字符串,所以我们可以预期这里的message既清晰又具有可读性。

异构枚举(Heterogeneous enums)

  • 枚举(Enums)是 TypeScript 提供的一个功能,允许开发者定义一组命名常量。

  • 异构枚举(Heterogeneous enums)是一种特殊类型的枚举,它们的成员值既可以是数字也可以是字符串。

举例说明:

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

在上面的异构枚举中,No 成员被赋予了数字 0,而 Yes 成员被赋予了字符串 "YES"

  • 异构枚举的使用很少见,因为这会导致枚举的值的类型不一致,可能会引发运行时的错误和混淆。

  • 在实际的应用中,推荐使用更加一致的枚举类型,即所有成员要么全是数字,要么全是字符串,这样可以减少潜在的错误并使代码更容易理解。

  • 如果确实需要在同一个枚举中同时使用数字和字符串作为值,你应该确保你非常清楚每个值如何被使用,并且在团队中制定明确的规则来避免混乱。

  • 尽管 TypeScript 支持异构枚举,但在 JavaScript 中没有等价的特性。当 TypeScript 代码编译到 JavaScript 时,异构枚举将变成一个对象,其中枚举的键是成员名称,而值则对应各个成员所赋的值。

下面是一个如何在实际代码中使用异构枚举的例子:

function getAnswer(value: boolean): BooleanLikeHeterogeneousEnum {
  if (value) {
    return BooleanLikeHeterogeneousEnum.Yes;
  } else {
    return BooleanLikeHeterogeneousEnum.No;
  }
}

// 使用函数
console.log(getAnswer(true)); // 输出 "YES"
console.log(getAnswer(false)); // 输出 0

在这个例子中,getAnswer 函数根据布尔参数返回相应的异构枚举成员。注意这样的代码可能难以为新人理解,因为它不遵循单一数据类型原则。

计算的和常量成员

枚举(Enumerations)在 TypeScript 中是一种特殊的类型,允许我们定义一组命名常量。当我们在编写代码时想要定义一组相关的值,并确保这些值可以轻松地互换使用而不会引起混淆时,就可以使用枚举。

枚举分为两种成员:计算的(Computed)和常量(Constant)成员。

  • 常量成员:
    • 在枚举的定义中,如果某个成员的值不是基于计算得到的,则它被认为是一个常量成员。
    • 常量枚举成员包括无初始赋值的成员以及值继承自之前枚举成员的情况。
    • 如果枚举的首个成员没有初始值,它会被赋予值0,后续未赋值的成员会递增 1。
enum Direction {
  Up, // 0
  Down, // 1
  Left, // 2
  Right, // 3
}
  • 计算的成员:
    • 计算的枚举成员是那些赋值了表达式的成员。
    • 如果一个枚举成员的值是通过一些计算得出的,则这个成员就是计算的枚举成员。
enum FileAccess {
  // 常量成员
  None,
  Read = 1 << 1,
  Write = 1 << 2,
  ReadWrite = Read | Write,
  // 计算的成员
  G = "123".length,
}

在上述FileAccess枚举中,ReadWriteReadWrite都是常量成员,因为它们都是直接赋值的。但是G成员是一个计算的枚举成员,因为它的值是通过运行"123".length这个表达式来确定的。

注意事项:

  • 计算成员后面的枚举成员必须设置初始值,因为 TypeScript 无法自动计算它们的值。
  • 枚举类型能够提供一个更贴近业务逻辑的数值集合,使代码可读性更强,并且限制了变量可以赋值的范围,从而减少错误。

理解和应用好常量和计算的枚举成员,可以让代码更加清晰和易于管理。

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

枚举(Enums)是 TypeScript 提供的一种特性,它允许你为一组数值定义友好的名字。这可以使代码更易于阅读和理解。

  • 联合枚举: 联合枚举是指由枚举成员构成的类型,其能够取得的值限定在特定的枚举成员上。当你声明一个变量为联合枚举类型时,该变量的值只能是枚举中定义的某个成员的值。

    enum Direction {
      Up,
      Down,
      Left,
      Right,
    }
    
    let a: Direction = Direction.Up; // a 的类型自动成为 Direction,只能赋值为 Direction 中的成员。
    
  • 枚举成员的类型: TypeScript 中的每一个枚举成员都有一个类型,当枚举成员不含字符串或计算值时,它们其实就是字面量类型。这意味着如果你只用了数字枚举成员,那么这些成员各自就代表了一个类型,并且枚举类型本身变得像是这些成员类型的联合。

    enum ShapeKind {
      Circle,
      Square,
    }
    
    interface Circle {
      kind: ShapeKind.Circle;
      radius: number;
    }
    
    interface Square {
      kind: ShapeKind.Square;
      sideLength: number;
    }
    
    let c: Circle = {
      kind: ShapeKind.Circle, // 正确,Circle.kind 的类型为 ShapeKind.Circle
      // kind: ShapeKind.Square, // 这会报错,因为 Circle.kind 只能是 ShapeKind.Circle 类型
      radius: 100,
    };
    

    在上面的例子中,Circle接口的kind属性必须是ShapeKind.Circle,因为它被赋予了该字面量类型。类似地,Square接口的kind属性必须是ShapeKind.Square

结合使用联合枚举和枚举成员的类型,可以有效地在 TypeScript 中进行类型保护,并确保变量的值符合某个特定的枚举成员,这样可以减少错误并提高代码质量。

运行时的枚举

枚举(Enums)是 TypeScript 提供的一个特性,它允许开发者定义一组命名的常量。使用枚举可以使代码更加清晰易读,同时也便于维护。

  • 运行时的枚举

    • TypeScript 的枚举在编译后会保留在运行时的代码中。这意味着你可以在程序运行时使用并操作这些枚举值。
    • 枚举类型在 JavaScript 中被表示为一个对象,其中枚举的每个成员都作为对象的一个属性存在。
  • 示例

假设有如下 TypeScript 枚举:

enum Color {
  Red,
  Green,
  Blue,
}

编译后的 JavaScript 代码将大致如下:

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

这里我们可以看到,枚举被编译成了一个立即执行的函数表达式(IIFE),并在函数内部给 Color 对象赋值。此外,数字枚举成员的值默认从 0 开始递增。

  • 在运行时可以通过枚举的名称来访问枚举值,反之亦然。例如:
let colorName: string = Color[2]; // 访问枚举成员的名称,输出 'Blue'
let colorValue: Color = Color.Blue; // 访问枚举成员的值,输出 2
  • 运行时的枚举使得开发者可以遍历枚举,处理动态设置的值,或者是在函数中接受枚举类型的参数。

  • 枚举类型提供了一个运行时的引用,可以用来在功能上实现一系列的工具,比如反向映射(从枚举值到枚举名)。

  • 注意事项

    • 使用枚举需要考虑到它们增加了编译后代码的体积,因为它们存在于运行时环境中。
    • 当不需要枚举的运行时特性时,可以使用 const enum 来定义。这样可以减少编译后的代码体积,因为 const enum 会在编译时被完全内联,但它不能被用在计算过的成员和在运行时环境中。

反向映射

枚举 (Enum) 是 TypeScript 提供的一个用于数据组织的特性。在 JavaScript 中没有直接的枚举概念,但 TypeScript 将其作为一种标准的构造加以实现。

  • 运行时的枚举

    • TypeScript 的枚举是在编译之后保留下来的,这意味着它们在编译生成的 JavaScript 代码中存在,并且可以在运行时使用。
    • 这与很多其他语言不同,其中枚举仅在编译时存在,而在运行时无法访问或使用。
  • 反向映射

    • 反向映射指的是 TypeScript 枚举的一项特殊功能,允许你从枚举值反查枚举名。
    • 默认情况下,TypeScript 不仅从字符串到枚举值进行映射,也从枚举值映射回到字符串。
    • 这种双向映射功能只适用于使用数字作为值的枚举成员。

例如,考虑以下的 TypeScript 枚举:

enum Status {
  New = 1,
  InProgress,
  Done,
}

// 正向映射:从枚举名到枚举值
let statusInProgress = Status.InProgress; // statusInProgress 的值将是 2

// 反向映射:从枚举值到枚举名
let statusName = Status[2]; // statusName 将是 'InProgress'

在上面的例子中,Status.InProgress 会被解析为数字 2,但同时也可以通过 Status[2] 来找到对应的枚举名 'InProgress'。这就是所谓的 "反向映射"。

需要注意的是,如果枚举的值是非数字值,则不会创建这样的反向映射。例如:

enum Colors {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE",
}

// 这里不存在反向映射,因为枚举的值是字符串

在这个例子中,Colors.Red 将得到字符串 "RED",但 Colors["RED"] 是不合法的,因为没有为字符串值创建反向映射。

const 枚举

枚举(Enums)是 TypeScript 提供的一个特性,它允许你定义一组命名常数。使用枚举可以让你的代码更具可读性和维护性。

运行时的枚举

  • TypeScript 的枚举在编译之后会保留在运行时的环境中。
  • 这意味着你可以在运行时的代码中使用枚举值和枚举名。

例如:

enum Color {
  Red,
  Green,
  Blue,
}

let c: Color = Color.Green;
console.log(c); // 输出:1
console.log(Color[c]); // 输出:"Green"

这段代码中,Color.Green被解析为1,因为默认情况下,枚举的值从0开始递增。同时,你还可以通过枚举的值来获取它的名称,比如Color[1]将会输出字符串"Green"

const 枚举

  • const枚举是一种特殊类型的枚举。
  • 当你希望避免额外的运行时开销,并且不需要枚举对象时,可以使用const枚举。
  • const枚举在编译阶段会被完全移除,枚举成员会在使用的地方被内联进来。

例如:

const enum Direction {
  Up,
  Down,
  Left,
  Right,
}

let directions = [
  Direction.Up,
  Direction.Down,
  Direction.Left,
  Direction.Right,
];

编译后的 JavaScript 代码将不包含Direction枚举,而只有它们所对应的值:

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

你会看到每个枚举成员都被替换成了一个注释和它的值,这样做减少了代码量,并且提升了运行时性能,因为不再需要查找对象属性。

需要注意的是,由于const枚举在编译后被删除,所以不能在运行时通过枚举对象进行反向映射。也就是说,你无法像运行时的枚举那样写Direction[Direction.Up]来获取枚举成员的名称。

外部枚举

外部枚举(Ambient Enums)

  • TypeScript 的外部枚举用来描述已经存在的枚举类型的形状。
  • 使用 declare enum 定义,这意味着它们在编译后的 JavaScript 中不生成任何代码。它们完全是为了在 TypeScript 里使用。
  • 外部枚举通常用于声明在当前 TypeScript 环境之外定义的枚举类型,例如当你需要从其他旧有代码库或原生环境中引入枚举时。

例子:

假设有一个已经在其他地方(如一个 JavaScript 库)定义好的枚举:

// 这是在JavaScript文件中定义的枚举
var FileAccess = {
  None: 0,
  Read: 1,
  Write: 2,
  ReadWrite: 3,
};

在 TypeScript 文件中,你可以使用外部枚举来描述上面的枚举:

declare enum FileAccess {
  None,
  Read,
  Write,
  ReadWrite,
}

function controlAccess(file: FileAccess) {
  // ...
}
  • 在这个例子中,虽然我们没有在 TypeScript 中定义具体的实现细节,但我们可以像使用普通枚举一样使用 FileAccess 枚举,能够提供类型安全的优势和自动补全的功能。

  • 需要注意的是,因为外部枚举不会编译成任何 JavaScript 代码,所以必须确保真正的枚举值在运行时是存在的,否则可能会导致运行时错误。