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/proposal-symbols-as-weakmap-keys.md.
  • 简体中文
  • Symbols as WeakMap keys S4

    中文标题:Symbols 作为 WeakMap 键

    提案概览
    提案速览

    该提案扩展了 WeakMap,允许使用唯一的 Symbol 作为键,解决了仅能将对象作为键的限制。它支持了更轻松的键创建、ShadowRealms 中基于膜的虚拟化以及从 Records 和 Tuples 引用对象等用例。

    Note

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

    Symbols 作为 WeakMap 键

    阶段 4

    联合作者/冠军:

    • Robin Ricard (@rricard)
    • Rick Button (@rickbutton)
    • Daniel Ehrenberg (@littledan)
    • Leo Balter (@leobalter)
    • Caridy Patiño (@caridy)
    • Rick Waldron (@rwaldron)
    • Ashley Claymore (@acutmore)

    规范文本


    介绍

    本提案扩展了 WeakMap API,允许使用唯一的 Symbol 作为键。

    目前,WeakMap 仅限于将对象作为键,这对 WeakMap 来说是一个限制,因为其目标是拥有可以被最终回收的唯一值。

    Symbol 是 ECMAScript 中唯一允许唯一值的原始类型。一个 symbol 值——比如通过调用 Symbol( [ description] ) 表达式产生的值——只能通过访问其原始产生式来识别。相同表达式的任何复制——使用相同的 description 值——都不会恢复任何先前产生式的原始值。这就是为什么我们说 symbol 值是独特的。

    对象被用作 WeakMap 的键,因为它们共享相同的身份方面。对象的身份只能通过访问原始产生式来验证,没有任何新对象能匹配预先存在的对象——例如——在严格比较中。

    早期讨论

    参见关于 Symbols 作为 WeakMap 键的早期讨论

    草案 PR

    参见当前包含建议规范的 ECMA-262 草案 PR

    用例

    易于创建和共享键

    无需创建一个仅用作键的新对象,symbol 将为 WeakMap 的人体工程学及其键和映射项的正确角色提供更清晰的说明。

    const weak = new WeakMap();
    
    // 无意双关:成为 symbol 使其更具象征性
    const key = Symbol('my ref');
    const someObject = { /* data data data */ };
    
    weak.set(key, someObject);

    ShadowRealms、Membranes 和虚拟化

    ShadowRealms 提案 不允许访问对象值。对于大多数虚拟化场景,膜系统建立在与 Realms 相关的 API 之上,使用 WeakMaps 连接引用。作为原始值的 Symbol 值仍然可访问,允许膜通过使用连接的身份来构建适当的弱映射。

    const objectLookup = new WeakMap();
    const otherRealm = new ShadowRealm();
    const coinFlip = otherRealm.evaluate(`(a, b) => Math.random() > 0.5 ? a : b;`);
    
    // 稍后...
    let a = { name: 'alice' };
    let b = { name: 'bob' };
    let symbolA = Symbol();
    let symbolB = Symbol();
    objectLookup.set(symbolA, a);
    objectLookup.set(symbolB, b);
    a = b = null; // 可以丢弃直接对象引用
    
    // 连接的标识在 symbol 通过其他 Realm 往返后得以保留
    let chosen = objectLookup.get(coinFlip(symbolA, symbolB));
    assert(['alice', 'bob'].includes(chosen.name));

    Record 和 Tuple

    本提案旨在解决 Record & Tuple 提案 引入的问题域;我们如何在原始值中引用和访问非原始值?

    简而言之,鉴于目前讨论中提出的所有约束,我们认为通过 WeakMaps 解引用的 Symbols 是从 Records 和 Tuples 引用对象的合理解析方式。

    关于这些应如何确切工作,以及有效的人体工程学/生态系统协调问题,我们希望能在 TC39 阶段进程中解决/验证。我们将从理解问题域开始,包括在没有此功能的情况下 Records 和 Tuples 为何是良好的第一步。然后,我们将探讨各种可能的解决方案及其优缺点。

    Records & Tuples 不能包含对象、函数或方法,当有人尝试这样做时会抛出 TypeError

    const server = #{
        port: 8080,
        handler: function (req) { /* ... */ }, // TypeError!
    };

    这个限制存在是因为 Record & Tuple 提案关键目标之一是默认具有深度不可变保证和结构相等性。

    下面提到的用户态解决方案提供了多种绕过此限制的方法,而无需额外的对象装箱语言支持,Record and Tuple 依然是可行且有用的。本提案试图描述补充这些用户态解决方案与 Record and Tuple 结合的方案,但不是将 Record and Tuple 引入语言的先决条件。

    接受 Symbol 值作为 WeakMap 键将允许 JavaScript 库实现自己的类 RefCollection 功能,这些功能可重用(避免到处传递映射,使用单个全局映射,仅传递 Records and Tuples),同时不会随时间泄漏内存。

    class RefBookkeeper {
        #references = new WeakMap();
        ref(obj) {
            // (简化;如果已经存在,我们可能想返回现有的 symbol)
            const sym = Symbol();
            this.#references.set(sym, obj);
            return sym;
        }
        deref(sym) { return this.#references.get(sym); }
    }
    globalThis.refs = new RefBookkeeper();
    
    // 使用
    const server = #{
        port: 8080,
        handler: refs.ref(function handler(req) { /* ... */ }),
    };
    refs.deref(server.handler)({ /* ... */ });

    作为 WeakMap 键的知名和注册 Symbols

    一些 TC39 代表在两个方向上都强烈争论。我们认为“允许”和“不允许”都是可接受的选项。

    注册 Symbols

    issue 21 中讨论了不允许注册 symbols。

    知名 Symbols

    允许知名 symbols 似乎并不那么糟糕,因为它们类似于在 Realm 生命周期内保持活跃的对象。在只要 JS 运行就保持活跃的 Realm 上下文(例如,在 Web 上,Worker 的 Realm)中,像 Symbol.iterator 这样的东西类似于 Object.prototypeArray.prototype 这样的原始元素。仅仅因为这些会保持活跃并不意味着我们禁止它们作为 WeakMap 键。

    虽然可以使用 Symbol.keyFor 检测“注册” symbols,但目前没有内置谓词来测试一个 symbol 是否为“知名”。如果“知名” symbols 不允许作为 WeakMap 键,代码需要确保处理这种潜在的中止完成。

    在 WeakRefs 和 FinalizationRegistry 中支持 Symbols

    我们还应该在 WeakRefs 和 FinalizationRegistry 中支持 Symbols。这不仅与 Objects 作为 WeakMap 键一致,还允许用户态构建/演示更高级的功能。例如,支持以 Records/Tuples 为键的 WeakMaps。

    总结

    我们认为将 Symbols 作为 WeakMap 键是一个有用、最小的原语,使 Records 和 Tuples 能够引用对象,同时尊重在单个 Realm 内支持基于膜隔离的目标所施加的约束。同时,用户态解决方案似乎满足了许多/大多数用例;我们相信 Records 和 Tuples 在没有任何额外的从原始值引用对象的机制的情况下非常有用,因此独立于本提案推进 Records 和 Tuples 是有意义的。