JavaScript使用Set合并对象数组时去重失效的解决方案
现有代码预期实现两个事件数组的无重复拼接功能,最终生成的集合中不应包含重复元素:data.CamData中存储了多个摄像头对象,每个对象具备CamID、Status及其他业务属性,其中Events数组内的每个元素结构固定为{Date: 1656533249379, State: 0}格式。
原有实现代码如下:
for (let i = 0; i < data.CamData.length; i++) { let hashSet = new Set(); data.CamData[i].Events.forEach((e) => hashSet.add(e)); state.camData[i].Events.forEach((e) => hashSet.add(e)); data.CamData[i].Events = Array.from(hashSet); }
问题现象
上述代码运行后仅完成了两个数组的简单拼接,未实现重复元素过滤,生成的集合中仍存在属性完全一致的重复事件项。
原因分析
Set数据结构对引用类型的去重判断基于对象引用地址,即使两个对象的属性、属性值完全一致,只要是内存中独立存储的两个对象、引用地址不同,Set就不会将其判定为重复项,因此无法实现对象内容层面的去重。
基于固定业务字段生成唯一键去重
事件对象的Date(时间戳)和State(状态)组合可以唯一标识一条事件,将两个字段拼接为字符串作为去重判断的唯一标识,从根源上绕开引用地址判断的限制。该方案性能最优,适合字段固定的业务场景,实现代码如下:for (let i = 0; i < data.CamData.length; i++) { const keySet = new Set(); const mergedEvents = []; // 合并两个来源的事件数组统一遍历 const allEvents = [...data.CamData[i].Events, ...state.camData[i].Events]; allEvents.forEach(event => { // 可根据业务唯一性规则调整组合的字段 const uniqueKey = `${event.Date}_${event.State}`; if (!keySet.has(uniqueKey)) { keySet.add(uniqueKey); mergedEvents.push(event); } }); data.CamData[i].Events = mergedEvents; }序列化对象作为去重键
如果事件对象的字段不固定,且不存在函数、循环引用、属性顺序随机的情况,可以直接将对象序列化为JSON字符串作为去重键,无需提前指定参与判断的字段。实现代码如下:for (let i = 0; i < data.CamData.length; i++) { const keySet = new Set(); const mergedEvents = []; const allEvents = [...data.CamData[i].Events, ...state.camData[i].Events]; allEvents.forEach(event => { const uniqueKey = JSON.stringify(event); if (!keySet.has(uniqueKey)) { keySet.add(uniqueKey); mergedEvents.push(event); } }); data.CamData[i].Events = mergedEvents; }注意:如果对象属性顺序不一致,即使内容完全相同,JSON序列化结果也会不同,此时需要先对对象的键做排序后再序列化,避免误判。
深比较逐行校验去重
如果业务场景不适合生成唯一键,可以在存入新元素前,将新元素和已存入结果集的所有元素逐个做深比较,确认不存在内容完全一致的元素后再存入。该方案灵活性最高,但数据量较大时性能开销明显高于前两种方案。
内容的提问来源于stack exchange,提问作者Yujin Dong

