专业编程教程与实战项目分享平台

网站首页 > 技术文章 正文

未来 JS 标准中的 Map.getOrInsert:彻底告别 if-else 判断?

ins518 2025-05-15 18:21:31 技术文章 19 ℃ 0 评论

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

使用 Map 或 WeakMap 时,一个常见问题是:当不确定键是否已存在于 Map 中时应该如何处理更新操作。

通常的做法是先检查键是否存在,然后根据检查结果进行插入或更新操作,但这对开发者来说既不方便也并非最佳方案,因为需要在 Map 中进行多次查找,而这些查找原本可以在一次调用中完成。

最近 T39 提出了一个 proposal-upsert 提案,其建议添加一个 getOrInsert 和 getOrInsertComputed 方法,如果键已存在于 Map 或 WeakMap 中,则返回与键关联的值,否则插入键并返回该值。下面是该提案列出的一系列的用例:

1. 使用 getOrInsert 处理默认值

getOrInsert 可以简化默认值的处理,因为其不会覆盖现有值。比如下面的代码经常会出现在代码中:

// 目前使用方式
let prefs = new getUserPrefs();
if (!prefs.has("useDarkmode")) {
  prefs.set("useDarkmode", true);
  // default to true
}

有了 getOrInsert 后,使用起来就非常简单:

// 使用 getOrInsert 方法
let prefs = new getUserPrefs();
prefs.getOrInsert("useDarkmode", true);
// default to true

通过使用 getOrInsert,开发者可以在不同时间应用默认值,并确保后续的默认值不会覆盖先前值。例如:在存在用户偏好、操作系统偏好设置和应用程序默认值的情况下,可以使用 getOrInsert 依次设置,而无需担心相互覆盖。

2. 使用 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,同时其还提供了处理对象以外的数据的灵活性。

const inventory = [
  {name: "asparagus", type: "vegetables", quantity: 9},
  {name: "bananas", type: "fruit", quantity: 5},
  {name: "goat", type: "meat", quantity: 23},
  {name: "cherries", type: "fruit", quantity: 12},
  {name: "fish", type: "meat", quantity: 22},
];

const restock = {restock: true};
const sufficient = {restock: false};
const result = Map.groupBy(inventory, ({ quantity}) =>
  quantity < 6 ? restock : sufficient,
);
console.log(result.get(restock));
// 输出 [{name: "bananas", type: "fruit", quantity: 5}]

3. 使用 getOrInsert 维护计数器

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);

4. 使用 getOrInsert 计算默认值

在某些场景下,确定默认值成本较高,此时可以使用 getOrInsertComputed 方法来达到该目的。

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

5.getOrInsert 的 polyfill

目前 proposal-upsert 提案还处于 Stage 2.7 阶段,如果需要使用 getOrInsert 或者 getOrInsertComputed 方法,可以使用借助于下面的 polyfill:

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

参考资料

https://github.com/tc39/proposal-upsert

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/groupBy

https://hackr.io/blog/javascript-map

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表