如何高效对比两个数组对象的数据差异?

爱学习的一硕 阅读 60

我从接口拿到了新旧两份用户配置数据,都是数组,每个元素是对象。想找出哪些项被修改、新增或删除了,但用 === 直接比较总是 false,因为引用不同。

试过遍历对比 id 和字段值,但代码又长又容易漏。有没有更简洁可靠的方案?比如用 Lodash 的 _.isEqual 配合 diff 逻辑?

const oldData = [{ id: 1, name: 'Alice', active: true }];
const newData = [{ id: 1, name: 'Alice Updated', active: false }, { id: 2, name: 'Bob' }];
我来解答 赞 6 收藏
二维码
手机扫码查看
2 条解答
Good“艺诺
这个问题其实挺常见的,直接比较引用确实不行,因为对象每次创建都是新的引用。

核心思路是这样的:把数组转换成 Map,用 id 当 key,这样查找效率是 O(1)。然后分别处理三种情况——新增、删除、修改。

先来看完整代码:

const oldData = [{ id: 1, name: 'Alice', active: true }];
const newData = [{ id: 1, name: 'Alice Updated', active: false }, { id: 2, name: 'Bob' }];

// 第一步:把旧数据转成 Map,key 是 id,value 是整个对象
// 这样后面查找的时候不用每次都遍历数组
const oldMap = new Map(oldData.map(item => [item.id, item]));

// 准备结果容器
const result = {
added: [], // 新增的项
removed: [], // 删除的项
modified: [], // 修改的项
unchanged: [] // 没变化的项
};

// 第二步:遍历新数据,逐个对比
for (const newItem of newData) {
const oldItem = oldMap.get(newItem.id);

if (!oldItem) {
// Map 里找不到,说明是新增的
result.added.push(newItem);
} else {
// 找到了,对比具体字段有没有变化
const changes = {};
let hasChange = false;

// 遍历新对象的所有键
for (const key of Object.keys(newItem)) {
// 注意:这里用 !== 比较基本类型足够了
// 如果涉及嵌套对象,需要递归比较
if (newItem[key] !== oldItem[key]) {
changes[key] = {
from: oldItem[key],
to: newItem[key]
};
hasChange = true;
}
}

if (hasChange) {
result.modified.push({
id: newItem.id,
before: oldItem,
after: newItem,
changes: changes
});
} else {
result.unchanged.push(newItem);
}
}
}

// 第三步:遍历旧数据,找出被删除的
// 新数据里没有的 id,就是被删除的
for (const oldItem of oldData) {
if (!newData.find(item => item.id === oldItem.id)) {
result.removed.push(oldItem);
}
}

console.log(result);


运行结果是这样的:

{
added: [{ id: 2, name: 'Bob' }],
removed: [],
modified: [{
id: 1,
before: { id: 1, name: 'Alice', active: true },
after: { id: 1, name: 'Alice Updated', active: false },
changes: {
name: { from: 'Alice', to: 'Alice Updated' },
active: { from: true, to: false }
}
}],
unchanged: []
}


如果你想用 Lodash,思路也差不多,用 _.differenceBy 快速找出新增和删除的项:

const _ = require('lodash');

// 找出新增的(在新数据里但不在旧数据里的)
const added = _.differenceBy(newData, oldData, 'id');

// 找出删除的(在旧数据里但不在新数据里的)
const removed = _.differenceBy(oldData, newData, 'id');

// 找出可能修改的(两边都有的 id)
const existingIds = new Set(newData.map(i => i.id));
const oldExisting = oldData.filter(i => existingIds.has(i.id));
const newExisting = newData.filter(i => existingIds.has(i.id));

// 逐个对比
const modified = [];
const unchanged = [];

for (const newItem of newExisting) {
const oldItem = oldExisting.find(i => i.id === newItem.id);
if (!_.isEqual(oldItem, newItem)) {
modified.push({ id: newItem.id, before: oldItem, after: newItem });
} else {
unchanged.push(newItem);
}
}


Lodash 方案的好处是 _.isEqual 能处理嵌套对象,但你需要额外装依赖。如果你只是简单的一维对象数组,上面原生写的方案就够了,逻辑清晰也好维护。

还有一点要注意:如果你的数据里有些字段是对象类型,比如 metadata: { role: 'admin' } 这样的,上面那个 !== 比较就不够用了。那时候要么用 Lodash 的 _.isEqual,要么自己写个递归比较函数。
点赞
2026-03-13 23:15
打工人慧芳
这个问题我也折腾过,当时搞了好久才找到一个靠谱的方案。直接 === 比较确实没用,因为比较的是内存地址。

Lodash 的 _.isEqual 可以深度比较对象内容,但确实不适合直接用来找差异。我后来用的方案是:

1. 先用 id 把数据转成字典格式方便查找
2. 分三种情况处理:新增、删除、修改

给你个实用代码片段:

function compareArrays(oldArr, newArr, idKey = 'id') {
const oldMap = new Map(oldArr.map(item => [item[idKey], item]));
const newMap = new Map(newArr.map(item => [item[idKey], item]));

const added = [];
const removed = [];
const changed = [];

// 找出新增的
for (const [id, item] of newMap) {
if (!oldMap.has(id)) added.push(item);
}

// 找出删除的
for (const [id, item] of oldMap) {
if (!newMap.has(id)) removed.push(item);
}

// 找出修改的
for (const [id, newItem] of newMap) {
const oldItem = oldMap.get(id);
if (oldItem && !_.isEqual(oldItem, newItem)) {
changed.push({
old: oldItem,
new: newItem
});
}
}

return { added, removed, changed };
}


用的时候直接 compareArrays(oldData, newData) 就能拿到分类好的结果。这个方法我用了好多次了,挺稳的。

注意几个坑:
1. 要确保所有对象都有 id 字段,不然会漏数据
2. 嵌套对象要用 _.isEqual 深度比较
3. 性能问题:数据量大的话要考虑优化,这个方案是 O(n) 的

如果不想用 Lodash,可以用 JSON.stringify 简单比较,但要注意顺序问题,有时候两个内容相同的对象 stringify 结果可能不同。
点赞
2026-03-08 12:05