配了多年 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+ 直接可用。
评论区
登录后可评论。