使用 `reduce` 实现数组去重时,对象数组无法正确去重,根本原因在于 JavaScript 中对象是引用类型,`===` 或 `==` 比较的是内存地址而非结构内容。例如:`[{id: 1}]` 中两个字面量对象 `{id: 1}` 虽然属性相同,但 `acc.includes(current)` 或 `acc.some(item => item === current)` 始终返回 `false`,导致重复对象被反复加入。`reduce` 本身无错,问题出在默认的浅层引用比较逻辑无法识别“逻辑相等”的对象。常见错误写法如 `arr.reduce((acc, cur) => acc.includes(cur) ? acc : [...acc, cur], [])` 在对象数组上完全失效。解决需显式定义唯一标识(如 `cur.id`)或深比较(慎用),更推荐结合 `Map` 或 `findIndex` 配合键值判重。本质是混淆了“引用相等”与“值相等”,属JS基础机制导致的典型陷阱。
1条回答 默认 最新
远方之巅 2026-02-17 17:25关注```html一、现象层:为何
reduce去重在对象数组上“看似失效”?开发者常写出如下代码:
const arr = [{id: 1, name: 'Alice'}, {id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}]; const unique = arr.reduce((acc, cur) => acc.some(item => item === cur) ? acc : [...acc, cur], [] ); // 结果仍是原数组长度:3 → 实际未去重!表面看是
reduce“不工作”,实则是===对两个独立字面量对象始终返回false——它们内存地址不同。二、机制层:JavaScript 引用类型与相等性语义的底层冲突
比较方式 作用对象 判定依据 对象数组适用性 ===所有类型 值(原始)或引用地址(对象) ❌ 完全不适用(同结构≠同引用) JSON.stringify()可序列化对象 字符串字面量是否一致 ⚠️ 有限适用(键序敏感、函数/undefined/Date丢失) 这揭示了本质矛盾:数组去重需“值相等”(value equality),而 JS 默认提供的是“引用相等”(reference equality)。
三、实践层:四种工业级解决方案对比
- 唯一键提取法(推荐 ✅):
arr.reduce((map, item) => map.has(item.id) ? map : map.set(item.id, item), new Map()).values() findIndex+ 键匹配法:
arr.filter((item, i) => arr.findIndex(v => v.id === item.id) === i)- Map 辅助判重(兼顾性能与可读):
const seen = new Map(); arr.filter(item => !seen.has(item.id) && seen.set(item.id, true)) - 深比较(仅限小规模、低频场景 ⚠️):
使用 LodashisEqual或自定义递归比对——但时间复杂度升至 O(n²×m),m 为对象嵌套深度。
四、架构层:从陷阱到范式——构建可复用的去重抽象
以下为生产环境推荐的泛型工具函数:
const uniqueBy = (arr, keyFn) => { const seen = new Set(); return arr.filter(item => { const key = typeof keyFn === 'function' ? keyFn(item) : item[keyFn]; if (seen.has(key)) return false; seen.add(key); return true; }); }; // 使用示例: uniqueBy(users, 'id'); // 按 id 去重 uniqueBy(users, u => `${u.id}-${u.status}`); // 复合键五、认知层:超越语法——重新理解“相等”的三重维度
graph TD A[JS 相等性] --> B[Reference Equality] A --> C[Shallow Value Equality] A --> D[Deep Value Equality] B -->|=== / ==| E[内存地址相同] C -->|JSON.stringify| F[字符串表示一致] D -->|递归遍历+类型感知| G[结构+值完全一致] style B fill:#ffebee,stroke:#f44336 style C fill:#e3f2fd,stroke:#2196f3 style D fill:#e8f5e9,stroke:#4caf50真正健壮的去重逻辑,必须显式声明采用哪一维度的相等性,并与业务语义对齐(如“用户去重=ID相同”,而非“对象结构相同”)。
六、演进层:ES2024+ 的潜在优化方向
- TC39 提案 Array.findLastIndex 可优化逆序去重逻辑
- 未来
Object.equals()标准化将提供原生深比较能力(当前 Stage 2) - WeakMap 驱动的缓存策略可避免重复计算哈希键(适用于超大对象集合)
技术债的本质,常是未显式建模的隐含假设——本例中,“对象相等”这一假设从未被代码明确定义。
```解决 无用评论 打赏 举报- 唯一键提取法(推荐 ✅):