乐从建网站免费搜索引擎推广方法有哪些
JavaScript Map 与 Set 深度解析
引言:为什么需要 Map 和 Set?
在 JavaScript 开发中,我们经常需要处理各种数据集合。传统的对象(Object)和数组(Array)虽然功能强大,但在某些场景下存在明显不足:
- 对象键名限制:只能使用字符串或 Symbol
- 数组查找效率低:包含性检查需要遍历整个数组
- 无法直接存储复杂数据结构
ES6 引入的 Map 和 Set 解决了这些问题,为开发者提供了更专业的数据结构工具。本文将深入探讨它们的区别、原理、性能和应用场景。
一、核心概念解析
1. Map:键值对集合
Map 是一种存储键值对的数据结构,其中键可以是任意类型(包括对象、函数等)。
const map = new Map();// 添加元素
map.set('name', 'Alice');
map.set(42, 'The Answer');
map.set({ id: 1 }, 'User Object');// 获取元素
console.log(map.get('name')); // "Alice"// 大小
console.log(map.size); // 3
2. Set:唯一值集合
Set 是一种存储唯一值的集合,不允许重复元素。
const set = new Set();// 添加元素
set.add('apple');
set.add('banana');
set.add('apple'); // 重复添加,会被忽略// 大小
console.log(set.size); // 2// 检查存在
console.log(set.has('banana')); // true
二、核心特性对比
特性 | Map | Set | 普通对象/数组 |
---|---|---|---|
键/值类型 | 任意类型 | 值任意 | 键:字符串/Symbol |
顺序性 | 插入顺序 | 插入顺序 | 对象无序,数组有序 |
重复值 | 键唯一 | 值唯一 | 对象键唯一,数组可重复 |
大小获取 | .size属性 | .size属性 | 对象:手动计算,数组:.length |
性能优化 | 高效查找 | 高效存在性检查 | 数组查找O(n) |
迭代 | 直接支持 | 直接支持 | 需要转换 |
三、详细方法与操作
1. Map方法详解
方法 | 描述 | 示例 |
---|---|---|
new Map() | 创建Map | const m = new Map() |
set(key, value) | 添加/更新元素 | m.set('a', 1) |
get(key) | 获取元素 | m.get('a') // 1 |
has(key) | 检查键存在 | m.has('a') // true |
delete(key) | 删除元素 | m.delete('a') |
clear() | 清空Map | m.clear() |
size | 元素数量 | m.size |
keys() | 返回键的迭代器 | [...m.keys()] |
values() | 返回值的迭代器 | [...m.values()] |
entries() | 返回键值对迭代器 | [...m.entries()] |
forEach() | 遍历方法 | m.forEach((v,k) => ...) |
2. Set方法详解
方法 | 描述 | 示例 |
---|---|---|
new Set() | 创建Set | const s = new Set() |
add(value) | 添加元素 | s.add(1) |
has(value) | 检查值存在 | s.has(1) // true |
delete(value) | 删除元素 | s.delete(1) |
clear() | 清空Set | s.clear() |
size | 元素数量 | s.size |
values() | 返回值迭代器 | [...s.values()] |
keys() | 同values() | [...s.keys()] |
entries() | 返回[value, value]迭代器 | [...s.entries()] |
forEach() | 遍历方法 | s.forEach(v => ...) |
四、性能对比分析
1. 查找性能测试
const size = 1000000;// 数组查找
const arr = Array(size).fill().map((_, i) => i);
console.time('Array find');
arr.includes(size - 1);
console.timeEnd('Array find'); // ≈ 5ms// Set查找
const set = new Set(arr);
console.time('Set find');
set.has(size - 1);
console.timeEnd('Set find'); // ≈ 0.01ms// 对象查找
const obj = {};
for (let i = 0; i < size; i++) obj[i] = true;
console.time('Object find');
obj.hasOwnProperty(size - 1);
console.timeEnd('Object find'); // ≈ 0.01ms// Map查找
const map = new Map();
for (let i = 0; i < size; i++) map.set(i, true);
console.time('Map find');
map.has(size - 1);
console.timeEnd('Map find'); // ≈ 0.01ms
2. 内存占用对比
结构 | 100万整数 | 特点 |
---|---|---|
Array | ~8MB | 紧凑存储 |
Set | ~16MB | 哈希表额外开销 |
Object | ~20MB | 键值存储效率低 |
Map | ~20MB | 类似对象 |
五、使用场景深度解析
1. Map的最佳场景
(1) 键需要复杂类型
const users = new Map();
const alice = { id: 1, name: 'Alice' };users.set(alice, { preferences: { theme: 'dark' } });
console.log(users.get(alice).preferences.theme); // "dark"
(2) 频繁增删键值对
const cache = new Map();function getData(key) {if (cache.has(key)) return cache.get(key);const data = fetchData(key); // 耗时操作cache.set(key, data);return data;
}
(3) 维护插入顺序的键值对
const orderedMap = new Map();
orderedMap.set('z', 1);
orderedMap.set('a', 2);console.log([...orderedMap.keys()]); // ['z', 'a'] (保持插入顺序)
2. Set的最佳场景
(1) 去重数组
const duplicates = [1, 2, 2, 3, 4, 4, 5];
const unique = [...new Set(duplicates)]; // [1,2,3,4,5]
(2) 集合运算
const setA = new Set([1, 2, 3]);
const setB = new Set([2, 3, 4]);// 并集
const union = new Set([...setA, ...setB]); // {1,2,3,4}// 交集
const intersection = new Set([...setA].filter(x => setB.has(x))
); // {2,3}// 差集
const difference = new Set([...setA].filter(x => !setB.has(x))
); // {1}
(3) 存储唯一标识
const visitedNodes = new Set();function traverse(node) {if (visitedNodes.has(node)) return;visitedNodes.add(node);// 处理节点...
}
六、常见面试题精析
1. 基础题:以下代码输出什么?
const map = new Map();
map.set(NaN, 'Not a Number');
map.set(NaN, 'Still Not a Number');console.log(map.size);
答案:1
解析:Map视NaN为相同键,后设置的值会覆盖前值
2. 陷阱题:对象作为键
const objKey = { id: 1 };
const map = new Map();
map.set(objKey, 'Value A');objKey.id = 2;
map.set(objKey, 'Value B');console.log(map.size);
console.log(map.get({ id: 1 }));
答案:
1
undefined
解析:
- 修改对象后,对象引用未变,所以第二次set是更新操作
- 新对象{id:1}与存储的键不是同一引用
3. 实现题:实现LRU缓存
class LRUCache {constructor(capacity) {this.capacity = capacity;this.map = new Map();}get(key) {if (!this.map.has(key)) return -1;const value = this.map.get(key);// 刷新使用顺序this.map.delete(key);this.map.set(key, value);return value;}put(key, value) {if (this.map.has(key)) {this.map.delete(key);} else if (this.map.size >= this.capacity) {// 删除最久未使用const oldest = this.map.keys().next().value;this.map.delete(oldest);}this.map.set(key, value);}
}
4. 综合题:数组交集高效实现
function intersection(arr1, arr2) {const set1 = new Set(arr1);return arr2.filter(x => set1.has(x));
}// 测试
console.log(intersection([1,2,3], [2,3,4])); // [2,3]
七、高级应用与技巧
1. 深度拷贝Map和Set
function deepCloneMap(original) {const clone = new Map();original.forEach((value, key) => {// 递归克隆键和值clone.set(typeof key === 'object' ? deepClone(key) : key,typeof value === 'object' ? deepClone(value) : value);});return clone;
}// Set类似实现
2. 序列化与反序列化
const map = new Map([['name', 'Alice'],[42, 'The Answer']
]);// 序列化
const serialized = JSON.stringify([...map]);// 反序列化
const deserialized = new Map(JSON.parse(serialized));
3. WeakMap与WeakSet
特性 | WeakMap | WeakSet |
---|---|---|
键/值类型 | 键必须是对象 | 值必须是对象 |
弱引用 | 是 | 是 |
不可迭代 | 是 | 是 |
使用场景 | 私有数据、缓存 | 临时对象标记 |
// WeakMap示例:私有数据存储
const privateData = new WeakMap();class Person {constructor(name) {privateData.set(this, { name });}getName() {return privateData.get(this).name;}
}
八、总结:如何选择Map或Set?
-
选择Map当:
- 需要键值对存储
- 键不是字符串/Symbol
- 需要保持插入顺序
- 频繁增删键值对
-
选择Set当:
- 需要存储唯一值
- 需要高效存在性检查
- 需要集合运算
- 去重数组元素
-
选择对象当:
- 键是字符串/Symbol
- 需要JSON序列化
- 需要遍历所有属性(包括原型链)
- 特殊需求如toString等内置方法
-
选择数组当:
- 有序集合
- 需要索引访问
- 需要栈/队列操作
“Map和Set不是替代对象和数组的工具,而是为解决特定问题设计的专业数据结构。”
随着JavaScript的发展,Map和Set已经成为现代前端开发不可或缺的工具。它们提供了:
- 更清晰的代码表达
- 更高效的性能
- 更强的功能
掌握它们的区别和适用场景,将使您在开发中游刃有余,并能在技术面试中脱颖而出。