配了多年 Map,默认值每次都要白跑一遍——getOrInsertComputed() 今天把这件事彻底变了

Map.getOrInsert() 是 ES2026 新增的 API,用来替代 has() → get() → set() 三步走的老写法。它把「键存在就读、不存在就插入」合并成一步操作,内部还做了引擎级优化。但它有一个隐藏的性能陷阱:默认值在调用时就立刻被计算,不管这个键是不是已经存在。

看下面这个例子:

“`javascript
const map = new Map([[“bar”, “foo”]]);

// 老写法
const result1 = map.getOrInsert(“bar”, expensiveFactory(“bar”));
// 每次调用,即使 bar 已存在,expensiveFactory() 也会被立即执行

// 新写法
const result2 = map.getOrInsertComputed(“bar”, expensiveFactory);
// 只有 bar 不存在时,expensiveFactory 才会被调用
“`

在 MDN 官方示例里,这个差异可以直接观察:

“`javascript
const map = new Map([[“bar”, “foo”]]);
const defaultCreator = (key) => {
console.log(Creating default for ${key});
return default for ${key};
};

map.getOrInsert(“bar”, defaultCreator(“bar”));
// → 输出: “Creating default for bar”(bar 已存在,但计算还是跑了一遍)

map.getOrInsertComputed(“bar”, defaultCreator);
// → 无输出(直接返回已有值,完全没触发计算)
“`

为什么这个差异在实际场景里很要命

getOrInsert() 的「立即计算」看似无害,但在以下场景里会造成真实的性能损耗:

1. 缓存 API 响应

“`javascript
// 把 API 响应缓存在 Map 里
const cache = new Map();

function getCachedData(key) {
// 旧写法:fetch 会每次都发,即使缓存命中也不会少跑
return cache.getOrInsert(key, fetch(/api/${key}));
}
“`

fetch 返回 Promise,每次调用 getOrInsert(key, fetch(...)) 都会新建一个 Promise 对象,即使这个 key 已经缓存了。

2. 默认值工厂有副作用

“`javascript
const counters = new Map();

function getCounter(id) {
// 旧写法:每次查值都会执行 () => ({ count: 0 })
return counters.getOrInsert(id, () => ({ count: 0 }));
}
“`

每次调用 getCounter("user_1") 都会产生一个新的对象引用——虽然 Map.get() 会返回已有值,但那个新的 { count: 0 } 对象已经被创建出来了,只是没被用上。

3. 复杂对象的初始化成本

“`javascript
const graph = new Map();

function ensureNode(nodeId) {
// 旧写法:每次查值都要 new Set() + DFS
return graph.getOrInsert(nodeId, buildSubgraph(nodeId));
}
“`

buildSubgraph() 如果涉及图遍历或正则编译,每次调用都是白跑。

基准测试:3 次操作 → 1 次操作

开发者 Jeff Bruchado 做了实测(1M 次 upsert 操作):

旧写法 has() + set() + get():
  耗时: 245ms
  每次操作: 3 次 Map 方法调用(hash + lookup 重复)

getOrInsert():
  耗时: 89ms
  每次操作: 1 次 Map 方法调用
  提升: 约 2.7x

getOrInsertComputed() 在这基础上进一步省去了「键存在时仍然计算默认值」的成本。

在默认值计算昂贵的场景下(比如正则编译、数据库查询),getOrInsertComputed 比 getOrInsert 还能再省下一整次工厂调用。

兼容性:2026 年全面可用

getOrInsertComputed() 已进入 Baseline 2026 Newly available,主流环境全覆盖:

环境 支持版本
Chrome / Edge 145+
Firefox 144+
Safari 26.2+
Bun 1.2.20+
Deno 2.6.7+
Node.js 26+

怎么迁移

“`javascript
// 旧写法(有性能问题)
function getValue(map, key) {
return map.getOrInsert(key, createExpensiveDefault(key));
}

// 新写法(lazy 计算)
function getValue(map, key) {
return map.getOrInsertComputed(key, createExpensiveDefault);
}
“`

把「立刻传入工厂函数返回值」改成「传入工厂函数本身」,仅此一处改动。

下一步

如果你在代码里搜到了这样的模式:

“`javascript
map.getOrInsert(key, expensiveOperation())
“`

把它改成:

“`javascript
map.getOrInsertComputed(key, expensiveOperation)
“`

即可白嫖一次引擎级优化 + 省去不必要的工厂调用。这个改动不需要任何依赖,Node.js 26、Bun 1.2.20+ 直接可用。

评论区

0 条评论

登录后可评论。

阿速·性能优化 12 阅读