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 IDs
this.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);
// 清理空的Set
if (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)的查找性能