1. JavaScript 中的 Map 数据结构:核心特性与 API
1.1 Map 的核心特性
在 JavaScript 的数据结构家族中,Map 是一个键值对集合,其中“键”可以是任意类型的值,包括对象、函数甚至是 NaN。这一点与普通的对象不同,后者的键通常是字符串或符号类型。插入顺序保持不变,也就是说遍历 Map 时会按加入的顺序返回键值对。
此外,Map 提供了直接判断键是否存在的能力:has 方法可以快速判定某个键是否被设置,size 属性返回当前键值对数量,get 可根据任意键检索值。以上特性使得 Map 在需要以对象作为键进行高效映射的场景中尤为有用。
1.2 Map 的常用 API
Map 的核心 API 包含:new Map()、set(key, value)、get(key)、has(key)、delete(key)、clear()、以及 size 属性。通过这些 API,可以实现对键值对的增删改查以及遍历控制。
下面演示一个最基本的 Map 用法,展示如何创建、插入、查询以及获取长度:映射长度、键的引用以及取值逻辑在代码中显性体现。

const map = new Map();const keyObj = {};
map.set(keyObj, '对象作为键的值');
map.set('name', 'Map 示例');
console.log(map.size); // 2
console.log(map.get(keyObj)); // '对象作为键的值'
console.log(map.has('name')); // true
对于遍历,for...of 循环、以及 entries()、keys()、values() 等方法都能实现按插入顺序访问键值对。
for (const [k, v] of map) {console.log(k, v);
}
console.log([...map.keys()]); // 显示所有键
console.log([...map.values()]); // 显示所有值2. JavaScript 中的 Set 数据结构:核心特性与 API
2.1 Set 的核心特性
Set 是一个值的集合,强调“唯一性”。其中的值可以是任意类型,且集合中的元素会按照插入顺序排列。Set 使用 SameValueZero 相等性来判断重复元素,两次赋予相同值的对象会被视作不同对象,但对于 NaN,Set 会将其视为同一个值从而去重。相比 Map,Set 的核心在于“存在性”判定。
Set 提供了添加、删除、清空、检查存在等操作:add、has、delete、clear,以及 size 属性,能够高效实现去重、集合成员判断等场景。
2.2 Set 的常用 API
创建 Set 的方式与 Map 类似,可以传入可迭代对象用于初始化,后续通过 add(value) 来添加新值,通过 has(value) 来判断是否存在,通过 delete(value) 移除某个值,通过 clear() 清空集合,最后通过 size 获取集合长度。下面给出一个去重示例以及基础操作。去重是 Set 的常见场景。
const nums = [1, 2, 2, 3, 4, 4, 5];
const set = new Set(nums);
console.log(set.size); // 5
console.log([...set]); // [1, 2, 3, 4, 5]set.add(6);
console.log(set.has(3)); // true
set.delete(2);
console.log([...set]); // [1, 3, 4, 5, 6]
3. Map 与 Set 的区别与对比,以及结合使用场景
3.1 键和值的语义与引用差异
Map 的键和值具有明确的语义区分:键可以是任意类型,而值无特殊限制;Set 只存储“值本身”,可理解为“唯一值集合”。在对象作为键的场景中,Map 通过引用来区分键,即便两个对象看起来结构相同,也会因为引用不同而被视为不同的键。下面示例直观呈现这一差异。
const m = new Map();
const a = { id: 1 };
const b = { id: 1 };m.set(a, 'A');
console.log(m.get(a)); // 'A'
console.log(m.get(b)); // undefined,a 与 b 是不同引用const s = new Set();
s.add(a);
s.add(b);
console.log(s.size); // 2,尽管 a 和 b 对象内容相同,但它们是不同引用
3.2 遍历与性能的差异
两者都提供高效的遍历能力,但使用场景不同。Map 的遍历回合包含键和值,适合需要同时访问键和值的场景;Set 只需要遍历值本身,更偏向于去重、成员判断等场景。性能方面,常见的增删查改操作在两者中都具有平衡的常数时间复杂度,但要根据实际使用的大小和访问模式做评估。
const map = new Map([['k1', 'v1'], ['k2', 'v2']]);
for (const [k, v] of map) {console.log(k, v);
}const set = new Set([1, 2, 3, 4]);
for (const v of set) {console.log(v);
}
3.3 结合使用的实用模式
将 Map 与 Set 结合使用,可以实现更丰富的结构,比如为用户分配多种权限、记录访问日志的去重集合、或对复杂对象的分组计数。通过 Map 的键作为对象标识,再配合 Set 进行去重和去重后的集合管理,可以实现高效且可读的代码结构。
// 示例:用户到权限的映射(Map>)
const userPermissions = new Map();
function grant(userId, perm) {if (!userPermissions.has(userId)) {userPermissions.set(userId, new Set());}userPermissions.get(userId).add(perm);
}
grant('u1', 'read');
grant('u1', 'write');
grant('u2', 'read');
console.log([...userPermissions.get('u1')]); // ['read', 'write']
4. 实战场景分析
4.1 使用 Map 的场景示例
当你需要以任意类型的键来快速检索对应的值时,Map 是首选。缓存与记忆化函数(memoization)是典型场景之一:通过将输入参数数组序列化,作为 Map 的键,存取计算结果以避免重复计算。下列示例展示了一个简单的记忆化装饰器逻辑。避免重复计算、提升性能。
function memoize(fn) {const cache = new Map();return function(...args) {const key = JSON.stringify(args);if (cache.has(key)) {return cache.get(key);}const result = fn.apply(this, args);cache.set(key, result);return result;};
}// 使用示例
const slow = n => {// 假设这是一个耗时操作return n * n;
};
const fast = memoize(slow);
console.log(fast(5)); // 25
console.log(fast(5)); // 直接从缓存返回 25
4.2 使用 Set 的场景示例
Set 的去重能力在处理数组去重、过滤重复值、实现简单的集合运算(并集、交集、差集)时非常方便。以下例子展示如何快速对数组去重并实现基本集合运算。
const arr = [1, 2, 2, 3, 4, 4, 5];
const unique = Array.from(new Set(arr));
console.log(unique); // [1, 2, 3, 4, 5]const a = new Set([1, 2, 3]);
const b = new Set([3, 4, 5]);// 并集
const union = new Set([...a, ...b]);
console.log([...union]); // [1, 2, 3, 4, 5]// 交集
const intersection = new Set([...a].filter(x => b.has(x)));
console.log([...intersection]); // [3]
4.3 Map 与 Set 的组合应用场景
在需要对多维数据进行分组、去重并快速检索时,Map 与 Set 的组合非常利落。下面的示例展示了如何把用户对象映射到一个去重后的标签集合中,并演示如何快速查询某个用户拥有的标签集合。
const users = [{ id: 1, name: 'Alice', tags: ['admin', 'editor'] },{ id: 2, name: 'Bob', tags: ['viewer'] }
];const userTags = new Map();
for (const u of users) {userTags.set(u.id, new Set(u.tags));
}console.log([...userTags.get(1)]); // ['admin', 'editor']
userTags.get(2).add('contributor');
console.log([...userTags.get(2)]); // ['viewer', 'contributor']


