1. Map与Set基础概念
Map:键值对集合
// 创建Map
const userMap = new Map();// 添加键值对
userMap.set('user1', { name: '张三', age: 25 });
userMap.set('user2', { name: '李四', age: 30 });// 获取值
console.log(userMap.get('user1')); // { name: '张三', age: 25 }// 检查是否存在
console.log(userMap.has('user1')); // true// 删除
userMap.delete('user2');
Set:唯一值集合
// 创建Set
const uniqueNumbers = new Set([1, 2, 3, 2, 4, 1]);// 添加元素
uniqueNumbers.add(5);
uniqueNumbers.add(3); // 重复元素不会被添加// 检查是否存在
console.log(uniqueNumbers.has(3)); // true// 删除元素
uniqueNumbers.delete(1);// 转换为数组
console.log([...uniqueNumbers]); // [2, 3, 4, 5]
2. Map与Object的对比
性能对比
// 测试Map vs Object性能
function performanceTest() {const map = new Map();const obj = {};const keys = Array.from({length: 10000}, (_, i) => `key${i}`);// Map性能测试console.time('Map set');keys.forEach(key => map.set(key, key));console.timeEnd('Map set');console.time('Map get');keys.forEach(key => map.get(key));console.timeEnd('Map get');// Object性能测试console.time('Object set');keys.forEach(key => obj[key] = key);console.timeEnd('Object set');console.time('Object get');keys.forEach(key => obj[key]);console.timeEnd('Object get');
}
主要优势对比
特性 | Map | Object |
---|---|---|
键类型 | 任意类型 | 字符串/符号 |
性能 | O(1) | O(1)但有哈希冲突 |
迭代 | 原生支持 | 需要Object.keys()等 |
大小 | size属性 | 需要手动计算 |
3. 实际应用场景
场景1:缓存系统
class Cache {constructor(maxSize = 100) {this.cache = new Map();this.maxSize = maxSize;}get(key) {if (this.cache.has(key)) {// LRU策略:将访问的项移到最后const value = this.cache.get(key);this.cache.delete(key);this.cache.set(key, value);return value;}return null;}set(key, value) {if (this.cache.size >= this.maxSize) {// 删除最老的项const firstKey = this.cache.keys().next().value;this.cache.delete(firstKey);}this.cache.set(key, value);}clear() {this.cache.clear();}
}// 使用示例
const cache = new Cache(3);
cache.set('data1', {id: 1, content: '...'});
cache.set('data2', {id: 2, content: '...'});
场景2:数据去重
// 数组去重
function uniqueArray(arr) {return [...new Set(arr)];
}// 对象数组去重(基于特定属性)
function uniqueByProperty(arr, property) {const seen = new Set();return arr.filter(item => {const value = item[property];if (seen.has(value)) {return false;}seen.add(value);return true;});
}// 使用示例
const numbers = [1, 2, 2, 3, 4, 4, 5];
console.log(uniqueArray(numbers)); // [1, 2, 3, 4, 5]const users = [{id: 1, name: '张三'},{id: 2, name: '李四'},{id: 1, name: '张三'}
];
console.log(uniqueByProperty(users, 'id'));
场景3:频率统计
class WordCounter {constructor() {this.wordCount = new Map();}countWords(text) {const words = text.toLowerCase().split(/\s+/);words.forEach(word => {const cleanWord = word.replace(/[^\w]/g, '');if (cleanWord) {this.wordCount.set(cleanWord, (this.wordCount.get(cleanWord) || 0) + 1);}});}getTopWords(n = 10) {return [...this.wordCount.entries()].sort((a, b) => b[1] - a[1]).slice(0, n);}getWordCount(word) {return this.wordCount.get(word.toLowerCase()) || 0;}
}// 使用示例
const counter = new WordCounter();
counter.countWords("hello world hello javascript world");
console.log(counter.getTopWords(3)); // [['hello', 2], ['world', 2], ['javascript', 1]]
场景4:关系映射
class Graph {constructor() {this.adjacencyList = new Map();}addVertex(vertex) {if (!this.adjacencyList.has(vertex)) {this.adjacencyList.set(vertex, new Set());}}addEdge(v1, v2) {this.addVertex(v1);this.addVertex(v2);this.adjacencyList.get(v1).add(v2);this.adjacencyList.get(v2).add(v1);}getNeighbors(vertex) {return this.adjacencyList.get(vertex) || new Set();}hasPath(start, end) {const visited = new Set();const queue = [start];while (queue.length > 0) {const current = queue.shift();if (current === end) return true;if (!visited.has(current)) {visited.add(current);const neighbors = this.getNeighbors(current);neighbors.forEach(neighbor => {if (!visited.has(neighbor)) {queue.push(neighbor);}});}}return false;}
}// 使用示例
const socialNetwork = new Graph();
socialNetwork.addEdge('Alice', 'Bob');
socialNetwork.addEdge('Bob', 'Charlie');
socialNetwork.addEdge('Alice', 'David');
console.log(socialNetwork.hasPath('Alice', 'Charlie')); // true
4. 高级应用技巧
迭代与遍历
// Map的遍历方式
const userMap = new Map([['user1', {name: '张三', age: 25}],['user2', {name: '李四', age: 30}],['user3', {name: '王五', age: 28}]
]);// 遍历键值对
for (const [key, value] of userMap) {console.log(`${key}: ${value.name}`);
}// 遍历键
for (const key of userMap.keys()) {console.log(key);
}// 遍历值
for (const value of userMap.values()) {console.log(value.name);
}// 使用forEach
userMap.forEach((value, key) => {console.log(`${key}: ${value.age}`);
});
复合数据结构
// Map嵌套Set
class TagManager {constructor() {this.tagToItems = new Map(); // tag -> Set of item IDsthis.itemToTags = new Map(); // item ID -> Set of tags}addTag(item, tag) {// 添加到tag->items映射if (!this.tagToItems.has(tag)) {this.tagToItems.set(tag, new Set());}this.tagToItems.get(tag).add(item);// 添加到item->tags映射if (!this.itemToTags.has(item)) {this.itemToTags.set(item, new Set());}this.itemToTags.get(item).add(tag);}getItemsByTag(tag) {return this.tagToItems.get(tag) || new Set();}getTagsByItem(item) {return this.itemToTags.get(item) || new Set();}removeTag(item, tag) {const itemTags = this.itemToTags.get(item);const tagItems = this.tagToItems.get(tag);if (itemTags) itemTags.delete(tag);if (tagItems) tagItems.delete(item);// 清理空的Setif (tagItems && tagItems.size === 0) {this.tagToItems.delete(tag);}}
}// 使用示例
const tagManager = new TagManager();
tagManager.addTag('post1', 'javascript');
tagManager.addTag('post1', 'web');
tagManager.addTag('post2', 'javascript');
console.log([...tagManager.getItemsByTag('javascript')]); // ['post1', 'post2']
性能优化技巧
// 批量操作优化
class BatchProcessor {constructor() {this.data = new Map();}// 批量设置batchSet(entries) {// 使用构造函数批量创建,比逐个set更高效const newMap = new Map([...this.data, ...entries]);this.data = newMap;}// 批量删除batchDelete(keys) {const newMap = new Map(this.data);keys.forEach(key => newMap.delete(key));this.data = newMap;}// 条件过滤filter(predicate) {const result = new Map();for (const [key, value] of this.data) {if (predicate(value, key)) {result.set(key, value);}}return result;}
}// 内存优化:弱引用
const weakMap = new WeakMap();
const weakSet = new WeakSet();// 适用于临时关联数据,不会阻止垃圾回收
function createPrivateData() {const privateData = new WeakMap();return class {constructor(data) {privateData.set(this, data);}getData() {return privateData.get(this);}};
}
5. 最佳实践建议
何时使用Map
// ✅ 适合使用Map的场景
// 1. 键是对象或非字符串类型
const objectKeys = new Map();
const obj1 = {id: 1};
const obj2 = {id: 2};
objectKeys.set(obj1, 'value1');
objectKeys.set(obj2, 'value2');// 2. 需要频繁增删操作
const frequentUpdates = new Map();
// Map的增删操作性能优于Object// 3. 需要知道集合大小
console.log(frequentUpdates.size); // 直接获取// 4. 需要保持插入顺序
// Map保持插入顺序,Object在某些情况下不保证
何时使用Set
// ✅ 适合使用Set的场景
// 1. 需要去重
const uniqueUsers = new Set(userIds);// 2. 需要快速查找
const allowedRoles = new Set(['admin', 'editor', 'viewer']);
if (allowedRoles.has(userRole)) {// 快速检查
}// 3. 集合运算
function setOperations(set1, set2) {// 并集const union = new Set([...set1, ...set2]);// 交集const intersection = new Set([...set1].filter(x => set2.has(x)));// 差集const difference = new Set([...set1].filter(x => !set2.has(x)));return { union, intersection, difference };
}
注意事项
// ❌ 常见错误
// 1. 使用Object作为Map的替代
const badMap = {}; // 当键是变量时容易出错
badMap[someObject] = 'value'; // 实际上是badMap['[object Object]']// 2. 忘记处理undefined
const map = new Map();
map.set('key', undefined);
console.log(map.has('key')); // true,但值是undefined
console.log(map.get('key')); // undefined// 3. 大量数据时的内存考虑
// 对于超大数据集,考虑分片或外部存储
总结
Map和Set是JavaScript中强大的数据结构,它们在以下场景特别有用:
- Map:适合键值对存储,特别是键不是字符串的情况,提供更好的性能和功能
- Set:适合去重和成员检查,提供O(1)的查找性能