
本文介绍一种健壮的对象深度差异检测方法,重点解决因数组、对象等引用类型直接使用 === 比较导致的误判问题,通过自定义数组内容比较逻辑,准确返回值发生变化的键及其新值。
在 javaScript 中进行对象比较时,一个常见却容易被忽视的陷阱是:引用类型(如数组、对象)的相等性判断默认基于内存地址,而非实际内容。这意味着即使两个数组结构完全一致、元素完全相同,只要它们是不同实例(例如从 API 重新获取或 jsON 序列化/反序列化后),oldObj.lotes === newObj.lotes 就会返回 false,从而错误地触发差异标记。
你提供的 getNew 函数正是受此影响——它用 oldObj[key] !== newObj[key] 粗粒度判断所有字段,对 imagens 和 lotes 这类数组字段天然失效。要真正“按值比较”,必须实现深度内容比对。
✅ 正确思路:区分数据类型,按需深度比较
我们需在遍历键值对时做类型判断:
- 对基础类型(String/number/Boolean/NULL/undefined),直接用 !==;
- 对数组,需逐项比对每个对象的属性与值;
- 对普通对象(非数组),可进一步扩展为递归比较(本文聚焦数组场景,但已预留可扩展结构)。
以下是一个生产就绪的改进版 getNew 函数,内建了专用于对象数组的严格值比较工具函数 isObjArrayValueEqual:
function isObjArrayValueEqual(arr1, arr2) { if (!Array.isArray(arr1) || !Array.isArray(arr2)) return false; if (arr1.length !== arr2.length) return false; for (let i = 0; i < arr1.length; i++) { const a = arr1[i], b = arr2[i]; // 要求同为非 null 对象 if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) { return false; } const keysA = Object.keys(a), keysB = Object.keys(b); if (keysA.length !== keysB.length) return false; for (const key of keysA) { if (a[key] !== b[key]) return false; // 仅支持浅层属性(适合本例) } } return true; } const getNew = (newObj, oldObj) => { // 边界处理:oldObj为空时全量返回 if (Object.keys(oldObj).length === 0 && Object.keys(newObj).length > 0) { return newObj; } const diff = {}; for (const key in oldObj) { // key 必须同时存在于 newObj 中才参与比较 if (!(key in newObj)) continue; const oldValue = oldObj[key]; const newValue = newObj[key]; if (newValue === oldValue) continue; // 基础类型快速通过 // 针对数组:使用内容比对 if (Array.isArray(newValue) && Array.isArray(oldValue)) { if (!isObjArrayValueEqual(newValue, oldValue)) { diff[key] = newValue; } continue; } // 其他引用类型(如 Date、Map、Set)或深层对象需额外处理 // (此处可引入 Lodash.isEqual 或自行实现递归 deepEqual) diff[key] = newValue; } return diff; // 注意:原逻辑中 return oldObj 是不合理的,应始终返回 diff(可能为空对象) };
? 使用示例与验证
const objA = { nome: "Lotes", lotes: [{ lote: "LOte0", loteQtd: "8" }], imagens: [{ extension: "jpeg", url: "https://example.com/1.jpeg" }] }; const objB = { nome: "Lotes", lotes: [{ lote: "LOte0", loteQtd: "8" }], // 内容完全一致 imagens: [{ extension: "jpeg", url: "https://example.com/1.jpeg" }] // 同样一致 }; console.log(getNew(objA, objB)); // → {}(空对象,无差异) const objC = { ...objA, imagens: [{ extension: "png", url: "https://example.com/2.png" }] // 修改了 extension }; console.log(getNew(objA, objC)); // → { imagens: [{ extension: "png", ... }] }
⚠️ 注意事项与进阶建议
- 本方案为浅层数组对象比较:假设数组内每个元素均为扁平对象(无嵌套对象/数组)。若存在深层嵌套,应升级为完整 deepEqual 实现(推荐使用 lodash.isequal)。
- 性能考量:频繁调用时,避免在循环中重复 Object.keys();可预缓存或使用 for…of + entries() 提升可读性。
- null/undefined 安全:当前 isObjArrayValueEqual 显式校验 null,确保鲁棒性;实际项目中建议统一用 Object.is() 替代 === 处理 NaN 等边界。
- 返回值语义清晰化:原函数在无差异时返回 oldObj,易引发混淆;改进版统一返回 diff(可能为空 {}),符合“差异即变更”的直觉。
通过这种类型感知+按需深度比对的设计,你就能精准捕获真实的数据变更点,彻底告别因引用相等性引发的假阳性差异报告。