news 2026/11/6 5:46:22

前端数组去重实战:从Set到Map的高效方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前端数组去重实战:从Set到Map的高效方案

1. 前端数组去重:从基础到高阶的实战指南

数组去重是前端开发中最基础却又最常遇到的问题之一。很多开发者习惯性地使用[...new Set(arr)]解决所有场景,但在实际业务中,数据规模、对象复杂度、性能要求等因素都会影响方案选择。本文将结合具体业务场景,分享我在电商平台开发中积累的数组去重经验。

2. 基础去重方案与适用场景

2.1 Set数据结构的基础应用

ES6引入的Set对象是最简单的去重方案:

const uniqueArray = [...new Set(originalArray)];

原理剖析:Set对象存储唯一值,自动过滤重复项。扩展运算符...将其转换回数组。

典型场景:

  • 商品分类标签去重(如['家电','数码','家电','服饰'])
  • 用户选择器选项去重
  • 简单字符串/数字数组处理

性能实测:在Chrome浏览器测试10,000个随机数去重仅需0.5ms左右,但存在以下局限:

  1. 无法处理对象引用({} === {}返回false)
  2. 大数据量时内存消耗显著增加
  3. 无法控制保留重复项的顺序

提示:在Vue/React的computed属性中使用Set去重时,建议添加缓存机制避免重复计算。

2.2 filter+indexOf的传统方案

arr.filter((item, index) => arr.indexOf(item) === index);

虽然代码直观,但存在O(n²)时间复杂度问题。实测10,000个元素需要200ms以上,不推荐在现代项目中使用。

3. 复杂对象去重实战

3.1 基于Map的键值去重

当处理对象数组时,需要指定唯一标识属性:

function uniqueByKey(arr, key) { const seen = new Map(); return arr.filter(item => { const identifier = item[key]; return !seen.has(identifier) && seen.set(identifier, true); }); } // 使用示例 const orders = [ {orderId: 'A100', product: 'Phone'}, {orderId: 'A101', product: 'Laptop'}, {orderId: 'A100', product: 'Tablet'} ]; uniqueByKey(orders, 'orderId'); // 结果保留前两个订单

优化技巧:

  1. 使用Map而非普通对象,支持非字符串键
  2. 采用短路求值避免二次查找
  3. 添加类型校验增强鲁棒性

3.2 多条件复合去重

实际业务中常需要多个字段组合作为唯一标识:

function uniqueByKeys(arr, keys) { const seen = new Set(); return arr.filter(item => { const compositeKey = keys.map(k => item[k]).join('|'); return !seen.has(compositeKey) && seen.add(compositeKey); }); } // 示例:按category和brand去重 const products = [ {id:1, category:'phone', brand:'Apple'}, {id:2, category:'phone', brand:'Samsung'}, {id:3, category:'phone', brand:'Apple'} // 重复 ];

4. 高性能去重方案

4.1 排序预处理法

对于10万+级别的数据,可先排序后去重:

function sortAndUnique(arr) { arr.sort(); // 可传入自定义排序函数 return arr.filter((item, i) => i === 0 || item !== arr[i-1]); }

性能对比:

方法10万数据耗时内存占用
Set120ms高
排序法80ms低

注意事项:

  1. 排序会改变原数组顺序
  2. 对象数组需要自定义比较函数
  3. 适合日志分析等顺序无关场景

4.2 位图法去重

适用于有限整数集合(如用户ID):

function bitmapUnique(arr) { const bitmap = []; return arr.filter(num => { const bytePos = num >> 3; const bitPos = num % 8; const mask = 1 << bitPos; if (bitmap[bytePos] & mask) return false; bitmap[bytePos] |= mask; return true; }); }

5. 特殊业务场景处理

5.1 保留最后出现的重复项

电商购物车合并场景常需保留最新添加的商品:

function keepLast(arr, key) { const seen = {}; // 反向遍历 return arr.reverse().filter(item => { const id = item[key]; return seen[id] ? false : (seen[id] = true); }).reverse(); // 恢复原顺序 }

5.2 自定义合并逻辑

更复杂的场景需要合并重复项而非简单丢弃:

function mergeDuplicates(arr, key, mergeFn) { const map = new Map(); arr.forEach(item => { const id = item[key]; map.set(id, map.has(id) ? mergeFn(map.get(id), item) : item); }); return [...map.values()]; } // 示例:合并订单金额 mergeDuplicates(orders, 'orderId', (existing, current) => ({ ...existing, amount: existing.amount + current.amount }));

6. 性能优化与边界处理

6.1 内存优化技巧

处理超大数组时:

  1. 使用对象池复用临时对象
  2. 分批处理(分页/lazy loading)
  3. Web Worker避免阻塞UI

6.2 类型安全增强

生产环境应添加防御性代码:

function safeUnique(arr, key) { if (!Array.isArray(arr)) return []; if (arr.some(item => item[key] === undefined)) { console.warn('Invalid key for uniqueness'); return arr; } // ...正常去重逻辑 }

7. 框架最佳实践

7.1 Vue中的响应式去重

// 在computed中使用 computed: { uniqueItems() { return this.keepLast(this.rawItems, 'id'); } } // 使用memorize避免重复计算 import { memorize } from 'lodash-es'; this.uniqueItems = memorize(uniqueByKey);

7.2 React性能优化

// 使用useMemo缓存结果 const uniqueItems = useMemo( () => uniqueByKey(rawItems, 'id'), [rawItems] ); // 自定义hook封装 function useUnique(items, key) { return useMemo(() => { const map = new Map(); return items.filter(item => !map.has(item[key]) && map.set(item[key], true)); }, [items, key]); }

8. 实际案例解析

8.1 电商平台商品筛选

需求:从不同渠道合并商品列表,要求:

  1. 按SKU去重
  2. 保留价格最低的版本
  3. 合并库存数量
const mergedProducts = mergeDuplicates( products, 'sku', (existing, current) => ({ ...existing, price: Math.min(existing.price, current.price), stock: existing.stock + current.stock }) );

8.2 数据分析场景

处理用户行为日志时需要:

  1. 按sessionId去重
  2. 保留最后一条记录
  3. 处理可能的数据损坏
function cleanLogs(logs) { return logs .filter(log => log.sessionId) // 过滤无效数据 .reduceRight((acc, log) => { // 从右向左遍历 if (!acc.seen.has(log.sessionId)) { acc.seen.add(log.sessionId); acc.result.push(log); } return acc; }, { seen: new Set(), result: [] }) .result .reverse(); // 恢复时间顺序 }

9. 测试与调试技巧

9.1 单元测试要点

describe('uniqueByKey', () => { it('应处理空数组', () => { expect(uniqueByKey([], 'id')).toEqual([]); }); it('应处理undefined键值', () => { const arr = [{id:1}, {noId:2}, {id:3}]; expect(uniqueByKey(arr, 'id').length).toBe(2); }); it('应保持原数组顺序', () => { const arr = [{id:3}, {id:1}, {id:3}, {id:2}]; expect(uniqueByKey(arr, 'id').map(i => i.id)).toEqual([3,1,2]); }); });

9.2 性能测试方法

// 生成测试数据 const largeArray = Array(1e6).fill().map((_,i) => ({ id: i % 1000, // 制造重复 value: Math.random() })); // 测试套件 console.time('Set去重'); [...new Set(largeArray.map(JSON.stringify))].map(JSON.parse); console.timeEnd('Set去重'); console.time('Map去重'); uniqueByKey(largeArray, 'id'); console.timeEnd('Map去重');

10. 工程化实践建议

  1. 工具函数封装:将常用去重方法封装为团队工具库
  2. TS类型支持:为不同场景添加精确的类型定义
  3. 文档规范:明确每种方法的适用场景和性能特征
  4. 异常监控:对可能的大数据操作添加性能监控
// TypeScript增强 interface UniqueOptions<T> { keep?: 'first' | 'last'; merge?: (existing: T, current: T) => T; } function advancedUnique<T>( arr: T[], key: keyof T, options?: UniqueOptions<T> ): T[] { // 实现... }

在大型前端架构中,合理的去重策略能显著提升应用性能。我曾在一个商品推荐模块中,通过优化去重算法将渲染性能提升了3倍。关键在于理解业务需求,选择时间复杂度与空间复杂度平衡的方案,而非盲目追求代码简洁。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/6 5:45:29

企业数据备份方案:恢复链、RTO/RPO与双机热备实战

简介&#xff1a;这份《企业数据备份方案文档》面向企业 IT 运维人员、网络管理员与信息化建设负责人&#xff0c;围绕企业业务网络如何长期安全稳定运行&#xff0c;梳理从备份策略设计到应急恢复落地的完整思路。正文以某实业公司的业务网络为实例&#xff0c;先说明网络备份…

作者头像 李华
网站建设 2026/11/6 5:45:17

MyBatis框架核心解析与数据库实体关系设计

1. MyBatis持久层框架核心设计解析作为Java生态中最主流的半自动化ORM框架&#xff0c;MyBatis在实际企业开发中承担着数据库访问桥梁的关键角色。不同于Hibernate的全自动化特性&#xff0c;MyBatis要求开发者必须亲自编写SQL语句&#xff0c;这种设计哲学带来了两个显著优势&…

作者头像 李华
网站建设 2026/11/6 5:44:30

两路DC/DC并联均流电源设计:最大电流法与MSP430闭环

简介&#xff1a;面向电子设计竞赛参赛者与开关电源方向学习者的2011年全国电子设计大赛A题完整论文&#xff0c;聚焦两路开关电源模块并联供电与自动均流系统的设计实现。文档以MSP430单片机为控制器、LM2576为电源芯片&#xff0c;围绕赛题基本要求与发挥部分展开方案论证&am…

作者头像 李华
网站建设 2026/11/6 5:44:25

推理模型与通用模型选型指南:CoT原理、提示语三层结构与需求匹配

简介&#xff1a;《DeepSeek&#xff1a;从入门到精通》围绕国产开源通用人工智能推理模型DeepSeek及其开源推理模型DeepSeek-R1展开&#xff0c;面向人工智能研发人员、工程师、学者以及希望提升大模型使用效率的开发者&#xff0c;帮助读者建立从基础认知到任务实操的完整框架…

作者头像 李华
网站建设 2026/11/6 5:44:05

4G/5G基站搬迁中BBU-RRU配置问题解决方案

1. 问题背景与现象描述在移动通信基站设备搬迁或改造过程中&#xff0c;基带处理单元&#xff08;BBU&#xff09;与射频拉远单元&#xff08;RRU&#xff09;的物理位置调整是常见操作。近期我们在某4G/5G混合组网项目中遇到一个典型问题&#xff1a;当BBU机房位置变更后&…

作者头像 李华
网站建设 2026/11/6 5:43:48

WPF中TCP请求-应答模式实现与问题解决

1. TCP请求-应答模式在WPF中的实战应用在工业自动化和设备控制领域&#xff0c;TCP通信是最常用的协议之一。作为一名长期从事工业软件开发的工程师&#xff0c;我经常需要在WPF应用中实现稳定可靠的TCP通信功能。本文将分享我在实际项目中积累的TCP请求-应答模式实现经验&…

作者头像 李华