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/year/2026/proposal-upsert.md.
  • 简体中文
  • Upsert S4

    提案概览
    提案速览

    该提案解决了在更新或插入之前检查 Map 或 WeakMap 中键是否存在的常见问题,这需要多次查找。它引入了 getOrInsert 和 getOrInsertComputed 方法,这些方法返回现有值或在一次调用中插入默认值或计算值。

    Note

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

    Upsert 提案

    ECMAScript 提案和参考实现,针对 Map.prototype.getOrInsertMap.prototype.getOrInsertComputedWeakMap.prototype.getOrInsertWeakMap.prototype.getOrInsertComputed

    作者: Daniel Minor (Mozilla) Lauritz Thoresen Angeltveit (Bergen) Jonas Haukenes (Bergen) Sune Lianes (Bergen) Vetle Larsen (Bergen) Mathias Hop Ness (Bergen)

    提案负责人: Daniel Minor (Mozilla)

    原始作者: Brad Farias (GoDaddy)

    前任提案负责人: Erica Pramer (GoDaddy)

    阶段: 4

    动机

    当使用 MapWeakMap 时,一个常见的问题是如何在不确定键是否已经存在于映射中时进行更新。这可以通过首先检查键是否存在,然后根据结果进行插入或更新来处理,但这对开发者来说既不方便,也不是最优的,因为它需要在映射中进行多次查找,而这本来可以通过一次调用完成。

    解决方案:getOrInsert

    我们提议添加一个方法,如果 key 已经存在于 MapWeakMap 中,则返回与 key 关联的值;否则插入 key 并附带提供的默认值,或调用提供的回调函数的结果,然后返回该值。

    此提案的早期版本有一个 getOrInsert 方法,提供两个回调,一个用于 insert,另一个用于 update,但当前的提案负责人认为,按需获取/插入是一个足够常见的用例,因此专注于这一点是有意义的,而不是试图创建一个最大灵活性的 API。它也强烈遵循其他语言中的先例,尤其是 Python。

    示例与提议的 API

    处理默认值

    使用 getOrInsert 简化了默认值的处理,因为它不会覆盖现有的值。

    // 当前
    let prefs = getUserPrefsMap();
    if (!prefs.has("useDarkmode")) {
      prefs.set("useDarkmode", true); // 默认为 true
    }
    
    // 使用 getOrInsert
    let prefs = getUserPrefsMap();
    prefs.getOrInsert("useDarkmode", true); // 默认为 true

    通过使用 getOrInsert,可以在不同的时间应用默认值,并保证后续的默认值不会覆盖现有的值。例如,在有用户偏好、操作系统偏好和应用程序默认值的情况下,我们可以使用 getOrInsert 依次应用用户偏好、操作系统偏好和应用程序默认值,而无需担心覆盖用户的偏好。

    增量分组数据

    一个典型的用例是根据键将数据分组,随着新值的出现。通过指定默认值而不必在尝试更新之前检查键是否存在于 Map 中,这得到了简化。

    // 当前
    let grouped = new Map();
    for (let [key, ...values] of data) {
      if (grouped.has(key)) {
        grouped.get(key).push(...values);
      } else {
        grouped.set(key, values);
      }
    }
    
    // 使用 getOrInsert
    let grouped = new Map();
    for (let [key, ...values] of data) {
      grouped.getOrInsert(key, []).push(...values);
    }

    诚然,这种模式的常见用例已经被 Map.groupBy 覆盖。然而,该方法要求所有数据在构建分组之前可用;使用 getOrInsert 将允许 Map 增量构建和使用。它还提供了灵活性,可以处理对象以外的数据,例如上面的数组示例。

    维护计数器

    另一个常见的用例是维护与特定键关联的计数器。使用 getOrInsert 使这更加简洁,并且是一种易于引擎优化的访问然后修改的模式。

    // 当前
    let counts = new Map();
    if (counts.has(key)) {
      counts.set(key, counts.get(key) + 1);
    } else {
      counts.set(key, 1);
    }
    
    // 使用 getOrInsert
    let counts = new Map();
    counts.set(key, counts.getOrInsert(key, 0) + 1);

    计算默认值

    对于某些用例,确定默认值可能是一个代价高昂的操作,如果不会使用它,最好避免。在这种情况下,我们可以使用 getOrInsertComputed

    // 使用 getOrInsertComputed
    let grouped = new Map();
    for (let [key, ...values] of data) {
      grouped.getOrInsertComputed(key, () => []).push(...values);
    }

    其他语言中的实现

    类似的功能在其他语言中也存在。

    Java

    Scala

    C++

    • emplace 如果缺失则插入
    • map[] assignment opts 如果缺失则插入 在 key 处,但如果 key 处存在值,则返回该值
    • insert_or_assign 如果缺失则插入。通过替换为特定的新值来更新现有值,而不是通过将函数应用于现有值

    C#

    • GetOrAdd 如果键不存在,则添加键/值对并返回新值;如果键已存在,则返回现有值。

    Rust

    • and_modify 提供对已占用条目的就地可变访问
    • or_insert_with 如果为空则插入。插入值来自映射函数

    Python

    • setdefault 执行 getinsert
    • defaultdict dict 的子类,带有一个回调函数,用于在 get 时构造缺失的值。

    Elixir

    • Map.update/4 如果键存在,则使用给定函数更新该项,否则插入给定的初始值

    规范

    Polyfill

    该提案可以简单地进行 polyfill:

    Map.prototype.getOrInsert = function (key, defaultValue) {
      if (!this.has(key)) {
        this.set(key, defaultValue);
      }
      return this.get(key);
    };
    
    Map.prototype.getOrInsertComputed = function (key, callbackFunction) {
      if (!this.has(key)) {
        this.set(key, callbackFunction(key));
      }
      return this.get(key);
    };