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