JavaScript 深度解析:如何比较两个 JSON 对象
在 JavaScript 开发中,比较两个 JSON 对象是否“相等”是一个常见需求,但 JSON 对象本质上是 JavaScript 的 Object 类型,其比较逻辑远比基本类型(如字符串、数字)复杂——因为对象是引用类型,直接使用 或 只能判断引用是否相同,而无法判断内容是否一致,本文将系统介绍 JavaScript 中比较两个 JSON 对象的多种方法,从基础到进阶,并分析其适用场景与注意事项。
为什么直接比较 或 会失效?
在 JavaScript 中,(严格相等)和 (宽松相等)对对象的比较规则是:判断两个对象的内存地址是否相同,而非比较其属性值。
const obj1 = { name: "Alice", age: 25 };
const obj2 = { name: "Alice", age: 25 };
const obj3 = obj1;
console.log(obj1 === obj2); // false(不同的内存地址)
console.log(obj1 === obj3); // true(obj3 是 obj1 的引用)
即使 obj1 和 obj2 的内容完全相同,它们也是两个独立的对象实例, 返回 false,这正是比较 JSON 对象时需要特殊处理的原因。
基础方法:递归比较属性值
核心思路
既然直接比较对象引用无效,我们可以通过递归遍历对象的属性,逐一比较每个属性的值是否相等,具体步骤如下:
- 检查两个对象是否为同一引用(直接返回
true)。 - 检查两个对象的类型是否一致(如一个对象、一个数组,则直接返回
false)。 - 比较属性数量是否相同(不同则直接返回
false)。 - 遍历一个对象的所有属性,检查另一个对象是否具有相同的属性值(递归处理嵌套对象)。
代码实现
function deepEqual(obj1, obj2) {
// 1. 引用相同,直接返回 true
if (obj1 === obj2) return true;
// 2. 检查是否为 null 或非对象类型(如字符串、数字等)
if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
return false;
}
// 3. 获取属性列表并比较数量
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
// 4. 递归比较每个属性值
for (const key of keys1) {
// 检查 obj2 是否有该属性,且递归比较值
if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) {
return false;
}
}
return true;
}
使用示例
const objA = { name: "Bob", info: { age: 30, city: "New York" } };
const objB = { name: "Bob", info: { age: 30, city: "New York" } };
const objC = { name: "Bob", info: { age: 30, city: "London" } };
console.log(deepEqual(objA, objB)); // true
console.log(deepEqual(objA, objC)); // false(info.city 不同)
局限性
- 未考虑原型链属性:
Object.keys()只会获取对象自身的可枚举属性,不会遍历原型链上的属性,如果需要比较原型属性,需改用for...in循环(并配合hasOwnProperty判断)。 - 未处理特殊对象类型:如
Date、RegExp、Map、Set等,这些对象的“内容”可能需要特殊逻辑(Date对象应比较时间戳,而非字符串形式)。
进阶方法:处理特殊对象与场景
处理 Date、RegExp 等内置对象
对于 Date、RegExp 等对象,直接比较其属性值可能不准确。
const date1 = new Date("2023-01-01");
const date2 = new Date("2023-01-01");
console.log(date1 === date2); // false(不同的引用)
console.log(date1.getTime() === date2.getTime()); // true(时间戳相同)
改进后的 deepEqual 可以增加对特殊对象的判断:
function deepEqualAdvanced(obj1, obj2) {
if (obj1 === obj2) return true;
// 处理 Date 对象
if (obj1 instanceof Date && obj2 instanceof Date) {
return obj1.getTime() === obj2.getTime();
}
// 处理 RegExp 对象
if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
return obj1.source === obj2.source && obj1.flags === obj2.flags;
}
// 处理 Map 和 Set
if (obj1 instanceof Map && obj2 instanceof Map) {
if (obj1.size !== obj2.size) return false;
for (const [key, value] of obj1) {
if (!obj2.has(key) || !deepEqualAdvanced(value, obj2.get(key))) {
return false;
}
}
return true;
}
if (obj1 instanceof Set && obj2 instanceof Set) {
if (obj1.size !== obj2.size) return false;
for (const value of obj1) {
if (!obj2.has(value)) {
return false;
}
}
return true;
}
// 其他非对象类型直接比较
if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
return false;
}
// 普通对象比较逻辑(同上)
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (const key of keys1) {
if (!keys2.includes(key) || !deepEqualAdvanced(obj1[key], obj2[key])) {
return false;
}
}
return true;
}
处理数组(JSON 数组也是对象)
JSON 数组是 JavaScript 数组的超集(JSON 不支持 undefined,但 JS 数组可以包含 undefined),数组的比较需要额外考虑顺序:
function deepEqualWithArray(obj1, obj2) {
if (obj1 === obj2) return true;
// 处理数组
if (Array.isArray(obj1) && Array.isArray(obj2)) {
if (obj1.length !== obj2.length) return false;
for (let i = 0; i < obj1.length; i++) {
if (!deepEqualWithArray(obj1[i], obj2[i])) {
return false;
}
}
return true;
}
// 其他逻辑同 deepEqualAdvanced
// ...(省略重复代码,可调用 deepEqualAdvanced)
}
现成工具:Lodash 的 _.isEqual
在实际开发中,重复造轮子并非最佳选择,Lodash 作为 JavaScript 实用库工具,提供了成熟的 _.isEqual 方法,可以深度比较任意 JavaScript 值(包括对象、数组、日期、Map、Set 等),并处理循环引用问题。
安装 Lodash
npm install lodash # 或 CDN 引入 <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script>
使用示例
const _ = require('lodash');
const objX = { a: 1, b: { c: [2, { d: 3 }] } };
const objY = { a: 1, b: { c: [2, { d: 3 }] } };
const objZ = { a: 1, b: { c: [2, { d: 4 }] } };
console.log(_.isEqual(objX, objY)); // true
console.log(_.isEqual(objX, objZ)); // false
优势
- 全面支持:内置对
Date、RegExp、Map、Set、Arguments等对象的特殊处理。 - 循环引用:能正确处理对象循环引用(如
obj.a = obj),避免栈溢出。 - 性能优化:内部采用多种优化策略(如先比较引用、再比较类型等),效率高于手写递归。
性能对比与选择建议
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
手写递归 (deepEqual) |
无依赖 |



还没有评论,来说两句吧...