For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/stage/1/proposal-enum.md.
  • 简体中文
  • Enums S1

    中文标题:枚举

    提案概览
    提案速览

    该提案向 ECMAScript 引入 enum 声明,提供有限集合的常量值用于判别式和标志,与 TypeScript 的枚举语法兼容。它定义了封闭、不可扩展的枚举对象,成员值类型受限(Number、String、Symbol、BigInt),支持自引用,并提供 Symbol.iterator 进行迭代。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    ECMAScript 枚举提案

    许多语言都有一个常见且经常使用的特性,即[枚举类型][],或 enum。枚举提供有限域的常量值,通常用于表示选择、判别式和位标志。作为 TypeScript 中一个流行且被广泛使用的特性,本提案旨在采用与 TypeScript 的 enum 声明兼容的形式。如果本提案的语法或语义与 TypeScript 有所不同,那是在充分了解 TypeScript 开发团队的情况下进行的,并且代表要么是 TypeScript 愿意采纳的变更,要么是 TypeScript 扩展的有限功能子集。

    注意:本提案已从其先前的版本进行了大量重做,先前的版本可以在 https://github.com/rbuckton/proposal-enum/tree/old 找到

    状态

    阶段: 1
    ** champions: ** Ron Buckton (@rbuckton)

    有关更多信息,请参阅 TC39 提案流程

    作者

    • Ron Buckton (@rbuckton)

    动机

    许多 ECMAScript 宿主和库通过各种判别式来区分类型或操作:

    • ECMAScript:
      • [Symbol.toStringTag]
      • typeof
    • DOM:
      • Node.prototype.nodeType (Node.ATTRIBUTE_NODENode.CDATA_SECTION_NODE 等)
      • DOMException.prototype.code (DOMException.ABORT_ERRDOMException.DATA_CLONE_ERR 等)
      • XMLHttpRequest.prototype.readyState (XMLHttpRequest.DONEXMLHttpRequest.HEADERS_RECEIVED 等)
      • CSSRule.prototype.type (CSSRule.CHARSET_RULECSSRule.FONT_FACE_RULE 等)
      • Animation.prototype.playState ("idle""running""paused""finished")
      • ApplicationCache.prototype.status (ApplicationCache.CHECKINGApplicationCache.DOWNLOADING 等)
    • NodeJS:
      • Buffer 编码 ("ascii""utf8""base64" 等)
      • os.platform() ("win32""linux""darwin" 等)
      • "constants" 模块 (ENOENTEEXIST 等;S_IFMTS_IFREG 等)

    此外,随着最近 NodeJS 中采用 TypeScript Type Stripping,人们对 ECMA-262 采用与 TypeScript 的 enum 声明兼容的形式重新产生了兴趣,因为它是最常用的 TypeScript 功能之一,但在这种模式下不支持。

    enum 声明相对于对象字面量提供了几个优势:

    • 默认封闭域 — 声明是不可扩展的,枚举成员将是不可配置和不可写的。
    • 受限的允许值域 — 将初始化器限制为允许的 JS 值的子集 (NumberStringSymbolBigInt)。
    • 定义期间的自引用 — 在后续枚举成员的初始化器中引用同一枚举的先前枚举成员。
    • 静态类型(工具)— 像 TypeScript 这样的静态类型系统使用枚举声明来判别类型,在悬停时提供文档等。
    • ADT 枚举(未来)— 未来可能支持代数数据类型枚举(即“可判别联合”)。
    • 装饰器(未来)— 未来可能支持特定于 enum 的装饰器。
    • 自动初始化器(未来)— 未来可能支持自动初始化的枚举成员。
    • “共享”枚举(未来)— 未来可能支持 shared enum,并对输入进行限制以与 shared struct 保持一致

    先前艺术

    语法

    虽然 Stage 1 提案通常鼓励避免特定语法,但本提案的一个既定目标是引入一种语法与 TypeScript 兼容的 enum 声明。因此,本提案的语法被限制为 TypeScript 的 enum 的一个子集。

    // enum 声明
    
    enum Numbers {
      zero = 0,
      one = 1,
      two = 2,
      three = 3,
      alsoThree = three // 自引用
    }
    
    enum PlayState {
      idle = "idle",
      running = "running",
      paused = "paused"
    }
    
    enum Symbols {
      alpha = Symbol("alpha"),
      beta = Symbol("beta") 
    }
    
    enum Named {
      Identifier = 0,
      "string name" = 1,
    }
    
    // 访问枚举值:
    let x = Numbers.three;
    let y = Named["string name"];
    
    // 迭代,取代 TypeScript 枚举的“反向映射”(格式化、
    // 调试、诊断等):
    for (const [key, value] of Numbers) ...

    语义

    虽然 Stage 1 提案通常鼓励避免特定语义,但本提案的一个既定目标是引入一种语义与 TypeScript 兼容的 enum 声明。因此,本提案的语义旨在尽可能与 TypeScript 的 enum 保持一致。

    枚举声明

    枚举声明由一组有限的_枚举成员_组成,这些成员定义了枚举的每个成员的名称和值。这些结果存储为_枚举对象_的属性。_枚举对象_是一个普通对象,其 [[Prototype]] 为 null。每个枚举成员在_枚举对象_上定义一个属性。

    此外,_枚举对象_包含一个 Symbol.iterator 方法,该方法按文档顺序为每个声明的枚举成员生成一个 [key, value] 条目。为了解释 Symbol.iterator 方法的语义,_枚举对象_可能需要一个 [[EnumMembers]] 内部槽。

    枚举成员

    枚举成员定义了属于枚举域的值的集合。每个枚举成员由一个名称和一个定义与该名称关联的值的初始化器组成。枚举成员是 [[Writable]]:false 和 [[Configurable]]:false

    枚举成员名称

    枚举成员名称目前仅限于 IdentifierNameStringLiteral,因为它们是 TypeScript 的 enum 目前支持的唯一成员名称。如果将来有足够的动机,我们可能会选择扩展以允许其他成员名称,如 ComputedPropertyName

    枚举不能有重复的成员名称。如果将来我们认为有必要支持 ADT 枚举,我们可能会选择引入对成员名称(如 constructor)的限制。

    如果枚举成员名称与枚举本身的名称具有相同的字符串值,则它会在枚举体内遮蔽枚举声明的名称。

    枚举成员初始化器

    枚举成员的初始化器被限制为 ECMAScript 值的子集(即 NumberStringSymbolBigInt)。这个限制使我们能够考虑未来在枚举中支持代数数据类型(ADT),而不会与某个枚举成员的值是函数之类的可能性冲突。

    枚举成员初始化器中的 IdentifierReference 可以引用先前声明的名称,也可以引用枚举本身(很像 class)。

    API

    除了 enum 声明本身之外,没有其他提议的 API。

    enum 声明将有一个 Symbol.iterator 方法,可用于迭代枚举成员的关键/值对。

    去糖化

    enum 声明可能实现为以下去糖化:

    enum E {
      A = 1,
      B = 2,
      C = A | E.B,
    }
    
    let E = (() => {
      let E = Object.create(null), A, B, C;
      Object.defineProperty(E, "A", { value: A = 1 });
      Object.defineProperty(E, "B", { value: B = 2 });
      Object.defineProperty(E, "C", { value: C = A | E.B });
      Object.defineProperty(E, Symbol.iterator, {
        value: function* () {
          yield ["A", E.A];
          yield ["B", E.B];
          yield ["C", E.C];
        }
      });
      Object.defineProperty(E, Symbol.toStringTag, { value: "E" });
      Object.preventExtensions(E);
      return E;
    })();

    其他考虑因素

    enum 表达式

    虽然 ECMAScript 对 classfunction 声明都有语句和表达式形式,但本提案目前不支持 enum 表达式。TypeScript 中没有 enum 表达式的概念,但如果有足够的动机,我们可能会考虑为 ECMAScript 引入 enum 表达式。

    export/export default

    enum 声明将支持 exportexport default,很像 class

    与共享结构的交互

    总的来说,本提案希望将枚举成员值与可以共享的 shared struct 对齐,但重要的是要注意,使用 Symbol() 而不是 Symbol.for()Symbol 值枚举将产生无法在两个代理之间协调的值。此外,ADT 枚举可能需要包含非共享数据,例如在 OptionResult 枚举中。因此,本提案可能会寻求引入 shared enum 声明,进一步限制允许的输入。

    与 TypeScript 的差异

    本提案的 enum 声明与 TypeScript 的 enum 有几个差异:

    自动初始化器

    TypeScript 的 enum 支持枚举成员的自动初始化:

    enum Numbers {
      zero, // 0
      one,  // 1
      two   // 2
    }

    然而,这种行为在一些 TC39 代表中是有争议的,并且已从本提案中删除。提出的主要担忧是,在现有枚举中间引入新的自动初始化枚举成员可能会在包中产生版本控制问题,并且这种行为应该更难实现,而不是作为默认行为。然而,即使不支持此功能,TypeScript 将继续支持自动初始化,因为它在开发人员社区中经常使用,但会向 JavaScript 发出显式初始化器。未来可能会引入另一种形式的自动初始化,TypeScript 和 ECMAScript 都可以利用。有关更多信息,请参阅未来方向部分中的自动初始化器主题。

    声明合并

    TypeScript(从 v5.8 开始)允许 enum 声明与同名其他 enum(和 namespace)声明合并。这不是 ECMAScript 枚举的理想功能,将不支持。TypeScript 正在考虑在 TypeScript 6.0 中弃用此功能

    反向映射

    TypeScript 目前支持使用 E[value] 将枚举值反向映射回枚举成员名称,但仅适用于 Number 值。此限制旨在避免 String 值枚举成员可能覆盖其他成员的冲突。虽然此信息对调试、诊断、格式化和序列化非常宝贵,但与整个 enum 相比,它的使用频率要低得多。

    为了避免这种不一致,我们改为建议使用迭代(通过 Symbol.iterator 内置符号)来覆盖“反向映射”案例:

    enum E {
      A = 0,
      B = "A",
    }
    
    for (const [key, value] of E) {
      console.log(`${key}: ${value}`);
    }
    
    // 打印:
    //  A: 0
    //  B: A
    
    const keyForA = E[Symbol.iterator]().find(([, value]) => value === "A")[0]
    console.log(keyForA); // 打印:B

    如果采用,TypeScript 将添加对 Symbol.iterator 的支持,同时最终弃用现有的反向映射支持。

    const enum

    TypeScript 支持 const enum 声明的概念,它与普通 enum 声明类似,只是枚举值被内联到它们的使用点。实现可以自由地根据需要进行优化,并且实现最终支持在普通 enum 声明上进行类似的内联是完全合理的。由于当前的 const enum 需要整个程序的知识和类型系统,我们认为它目前应该仍然是 TypeScript 特有的能力。

    Symbol

    TypeScript 目前不支持枚举的 Symbol 值,但如果采用此功能,将添加支持。

    BigInt

    TypeScript 目前不支持枚举的 BigInt 值,但如果采用此功能,将添加支持。

    export default

    TypeScript 目前不支持枚举的 export default,但如果采用此功能,将添加支持。

    未来方向

    虽然本提案的范围相当有限,但有以下几个潜在的后续提案领域:

    代数数据类型(ADT)枚举

    代数数据类型(ADT)枚举类似于结构化类型的可判别联合。ADT 枚举成员描述一个构造函数,该函数生成一个具有判别属性的对象。未来的 ECMAScript enum 声明增强可能会支持与 ExtractorsPattern Matching 结合的 ADT 枚举:

    enum Option {
      Some(value),
      None()
    }
    
    const opt = Option.Some(123);
    match (opt) {
      Option.Some(let value): console.log(value);
      Option.None(): console.log("<no value>");
    }
    
    
    enum Result {
      Ok(value),
      Error(reason)
    }
    
    function try_(cb) {
      try {
        return Result.Ok(_cb());
      } catch (e) {
        return Result.Error(e);
      }
    }
    
    const res = try_(() => obj.doWork());
    match (res) {
      Result.Ok(let value): ...;
      Result.Error(let reason): ...;
    }

    这里,Option.Some 可能描述一个“构造函数”,它生成一个由众所周知的符号字段或仅由其 [[Prototype]] 判别的对象,使得 Option.Some(0) instanceof Option.Sometrue。ADT 枚举成员还可以通过使用绑定模式来描述更复杂的形状,例如:

    enum Geometry {
      Point({ x, y }),
      Line(p1, p2),
    }
    
    const p1 = Geometry.Point({ x: 0, y: 1 });
    p1[0].x; // 0
    p1[0].y; // 1
    
    const p2 = Geometry.Point({ x: 2, y: 3 });
    const l = Geometry.Line(p1, p2);
    l[0] === p1; // true
    
    const printGeom = geom => match (geom) {
      Geometry.Point({ let x, let y }): console.log(`Point({ x: ${x}, y: ${y} })`);
      Geometry.Line(let p1, let p2): console.log(`Line(${printGeom(p1)}, ${printGeom(p2)})`);
    };
    
    printGeom(p1); // Point({ x: 0, y: 1 })
    printGeom(l); // Line(Point({ x: 0, y: 1 }), Point({ x: 2, y: 3 }))

    ADT 枚举成员可能还需要一种机制在 enum 上实现原型或静态方法,这也是我们更喜欢使用 Symbol.iterator 来描述 enum 的域而不是像 Object.entries() 这样的东西的原因之一。

    装饰器

    将来我们可能会选择将 Decorators 支持扩展到 enum 声明,以支持序列化/反序列化、格式化和 FFI 场景:

    @WasmType("u1")
    enum Role {
      @Alias(["user", "person"], { ignoreCase: true })
      user = 1,
      @Alias(["admin", "administrator"], { ignoreCase: true })
      admin = 2,
    }

    自动初始化器

    虽然本提案不支持 TypeScript 的自动初始化语义,但我们可能会考虑在未来的提案中引入替代语法,例如在本提案旧版本中描述的 of 子句:

    enum Numbers of Number { zero, one, two, three }
    Numbers.zero; // 0
    
    enum Colors of String { red, green, blue }
    Colors.red; // "red"

    或者通过某种静态可识别的语法:

    auto enum Numbers { zero, one, two, three }
    Numbers.zero; // 0

    历史

    TODO

    以下是推进 TC39 提案流程 各阶段的高层任务列表:

    Stage 1 进入标准

    • 确定了一位“ champion ”来推进添加。
    • 散文 概述了问题或需求以及解决方案的大致形状。
    • 说明性示例 的使用。
    • 高层 API

    Stage 2 进入标准

    Stage 3 进入标准

    Stage 4 进入标准

    • Test262 验收测试已为主要使用场景编写并合并
    • 两个兼容实现通过验收测试:[1][2]
    • 已向 tc39/ecma262 发送包含集成规范文本的拉取请求
    • ECMAScript 编辑器已签署拉取请求

    先前讨论