
本文介绍一种健壮的对象深度差异检测方法,重点解决因数组、对象等引用类型直接使用 === 比较导致的误判问题,通过自定义数组内容比较逻辑,准确返回值发生变化的键及其新值。
在 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(可能为空 {}),符合“差异即变更”的直觉。
通过这种类型感知+按需深度比对的设计,你就能精准捕获真实的数据变更点,彻底告别因引用相等性引发的假阳性差异报告。










