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) |
无依赖 |
抖音足球直播
抖音足球直播
企鹅直播
企鹅直播
足球直播
爱奇艺直播
爱奇艺足球直播
足球直播
足球直播
iqiyi直播
足球直播
足球直播
QQ足球直播
QQ足球直播
足球直播
足球直播
QQ足球直播
QQ足球直播
足球直播
足球直播
快连
快连
快连
快连下载
快连
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
新浪足球直播
新浪足球直播
足球直播
足球直播
有道翻译
有道翻译
有道翻译
有道翻译
wps
wps
wps
wps
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
新浪足球直播
新浪足球直播
足球直播
足球直播



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