一、Set 实用代码片段
1. 数组去重(基础版)
场景:接口返回数组、用户输入列表等需要快速去重。
javascript
运行
javascript
/**
* 数组去重(支持基本类型,引用类型需额外处理)
* @param {Array} arr - 待去重数组
* @returns {Array} 去重后数组
*/
const uniqueArray = (arr) => [...new Set(arr)];
// 示例
const arr = [1, 2, 2, 3, 'a', 'a'];
console.log(uniqueArray(arr)); // [1, 2, 3, 'a']
2. 检查数组是否有重复元素
场景:表单验证(如 "标签不可重复")、数据校验。
javascript
运行
javascript
/**
* 检查数组是否存在重复元素
* @param {Array} arr - 待检查数组
* @returns {boolean} 是否有重复
*/
const hasDuplicates = (arr) => new Set(arr).size !== arr.length;
// 示例
console.log(hasDuplicates([1, 2, 3])); // false
console.log(hasDuplicates([1, 2, 2])); // true
3. 集合操作(交集 / 并集 / 差集)
场景:权限对比(如 "用户权限与角色权限的交集")、数据筛选。
javascript
运行
ini
// 交集:两个数组的共同元素
const intersection = (arr1, arr2) => {
const set2 = new Set(arr2);
return arr1.filter(item => set2.has(item));
};
// 并集:两个数组的所有元素(去重)
const union = (arr1, arr2) => [...new Set([...arr1, ...arr2])];
// 差集:arr1 有但 arr2 没有的元素
const difference = (arr1, arr2) => {
const set2 = new Set(arr2);
return arr1.filter(item => !set2.has(item));
};
// 示例
const a = [1, 2, 3];
const b = [2, 3, 4];
console.log(intersection(a, b)); // [2, 3]
console.log(union(a, b)); // [1, 2, 3, 4]
console.log(difference(a, b)); // [1]
4. 临时存储 "已处理项"(避免重复操作)
场景:批量处理数据时记录已处理 ID,防止重复请求 / 计算。
javascript
运行
scss
// 记录已处理的任务ID
const processedTaskIds = new Set();
/**
* 处理任务(仅处理未处理过的)
* @param {number} taskId - 任务ID
*/
const processTask = (taskId) => {
if (processedTaskIds.has(taskId)) {
console.log(`任务 ${taskId} 已处理,跳过`);
return;
}
// 模拟处理逻辑
console.log(`处理任务 ${taskId}`);
processedTaskIds.add(taskId);
};
// 示例
processTask(1); // 处理任务 1
processTask(1); // 任务 1 已处理,跳过
processTask(2); // 处理任务 2
二、Map 实用代码片段
1. 复杂键名映射(替代对象的局限性)
场景:用对象(如 DOM 元素、实例)作为键存储数据(对象的键会被转为字符串,无法直接用对象当键)。
javascript
运行
dart
// 场景:给DOM元素绑定额外数据(如点击次数、状态)
const elementData = new Map();
// 获取DOM元素
const btn = document.getElementById('submit-btn');
const input = document.getElementById('username-input');
// 存储数据(键为DOM元素,值为任意类型)
elementData.set(btn, { clickCount: 0, disabled: false });
elementData.set(input, { value: '', touched: false });
// 更新数据
btn.addEventListener('click', () => {
const data = elementData.get(btn);
elementData.set(btn, { ...data, clickCount: data.clickCount + 1 });
console.log(`按钮点击次数:${elementData.get(btn).clickCount}`);
});
2. 接口数据缓存(避免重复请求)
场景:同一参数的接口请求,优先返回缓存数据,减少接口调用。
javascript
运行
javascript
/**
* 带缓存的接口请求工具
* @param {Function} fetchFn - 实际请求函数(返回Promise)
* @returns {Function} 包装后的请求函数
*/
const withCache = (fetchFn) => {
const cache = new Map(); // 缓存:键为参数字符串,值为请求结果
return async (...args) => {
// 生成唯一缓存键(将参数转为字符串,支持多参数)
const cacheKey = JSON.stringify(args);
// 命中缓存:直接返回
if (cache.has(cacheKey)) {
console.log('使用缓存数据');
return cache.get(cacheKey);
}
// 未命中:请求并缓存
console.log('发起新请求');
const result = await fetchFn(...args);
cache.set(cacheKey, result);
return result;
};
};
// 示例:包装一个获取用户信息的接口
const fetchUser = async (userId) => {
const res = await fetch(`/api/user/${userId}`);
return res.json();
};
// 使用缓存版请求
const fetchUserWithCache = withCache(fetchUser);
// 第一次请求(无缓存)
fetchUserWithCache(1);
// 第二次请求同一用户(用缓存)
fetchUserWithCache(1);
3. 多维度数据映射(快速查询)
场景:同一份数据需要通过多个 "键" 查询(如用户信息可通过 ID、手机号、用户名查询)。
javascript
运行
dart
// 原始用户数据
const users = [
{ id: 1, name: '张三', phone: '13800138000' },
{ id: 2, name: '李四', phone: '13900139000' }
];
// 构建多维度映射
const userMaps = {
byId: new Map(), // 键:id
byName: new Map(), // 键:name
byPhone: new Map() // 键:phone
};
users.forEach(user => {
userMaps.byId.set(user.id, user);
userMaps.byName.set(user.name, user);
userMaps.byPhone.set(user.phone, user);
});
// 快速查询示例
console.log(userMaps.byId.get(1)); // {id:1, name:'张三', ...}
console.log(userMaps.byPhone.get('13900139000')); // {id:2, ...}
4. 有序键值对遍历(保留插入顺序)
场景:需要按 "插入顺序" 遍历键值对(对象的键遍历顺序不稳定,尤其是数字键)。
javascript
运行
javascript
// 场景:按用户操作顺序记录日志(需保留顺序)
const actionLog = new Map();
// 按顺序插入操作
actionLog.set('login', { time: '09:00', user: '张三' });
actionLog.set('view', { time: '09:05', page: '首页' });
actionLog.set('logout', { time: '10:00', user: '张三' });
// 按插入顺序遍历(Map 会保留插入顺序)
for (const [action, detail] of actionLog) {
console.log(`${action}:${JSON.stringify(detail)}`);
}
// 输出顺序:login → view → logout(与插入顺序一致)
三、使用小贴士
-
选择 Set 还是 Map?
- 只需要 "唯一元素集合" → 用 Set;
- 需要 "键值对映射"(尤其是复杂键) → 用 Map。
-
性能考量:
- Set/Map 的
has/get/set操作时间复杂度为 O (1),比数组的indexOf、includes等(O (n))更高效,数据量大时优先使用。
- Set/Map 的
-
转换技巧:
- Set 转数组:
[...mySet]或Array.from(mySet); - Map 转对象(键为字符串时):
Object.fromEntries(myMap)。
- Set 转数组:
-
转换技巧:
- Set/Map 的
has/get/set操作都是 O (1),比数组indexOf(O (n))、对象循环查询快,数据量大(>100)时优先用。 - 临时缓存(比如接口缓存)如果不需要持久化,用 Map 即可;需要持久化到 localStorage,要先转成数组 / 对象(因为 localStorage 只能存字符串)。
- Set/Map 的
-
避坑提醒
- Set 存引用类型(对象、数组)时,不会自动去重(因为引用地址不同),比如
new Set([{a:1}, {a:1}])会存两个对象。 - Map 的键是 "引用相等",比如
map.set({}, 1)和map.set({}, 2)是两个不同的键(对象引用不同)。
- Set 存引用类型(对象、数组)时,不会自动去重(因为引用地址不同),比如