首页 > web前端 > js教程 > 正文

js怎么判断两个对象是否相等

煙雲
发布: 2025-08-17 14:30:02
原创
416人浏览过

javascript中直接使用==或===无法正确比较对象内容,因为它们只比较引用地址而非实际值;要实现内容相等判断,需进行深层比较。1. 首先检查引用是否相同,相同则返回true;2. 排除null或非对象类型,确保两者均为对象;3. 特殊处理date和regexp对象,分别比较时间戳和源码与标志;4. 对数组递归比较长度及每个元素;5. 对普通对象通过object.keys()获取自身属性并递归比较属性值;6. 使用hasownproperty确保不比较原型链上的属性。此外,实际开发中推荐使用lodash的_.isequal()或ramda的r.equals(),它们能处理循环引用、特殊类型并保证健壮性,而json.stringify()方法因属性顺序敏感、忽略undefined/函数/symbol且无法处理循环引用而不推荐使用。该方法完整支持对象、数组、date、regexp等内容一致性判断,最终返回布尔值结束。

js怎么判断两个对象是否相等

JavaScript中判断两个对象是否相等,并不能像比较基本类型那样直接使用

==
登录后复制
===
登录后复制
。这是因为在JavaScript里,对象(包括数组和函数)在内存中是按引用存储的,
==
登录后复制
===
登录后复制
比较的是这两个引用地址是否指向同一个内存空间。换句话说,即使两个对象拥有完全相同的属性和值,只要它们是不同的对象实例,直接比较的结果依然是
false
登录后复制
。要真正判断它们内容是否一致,我们需要进行“深层比较”,也就是逐个属性地去核对。

解决方案

要判断两个JavaScript对象是否在内容上相等,核心思路是递归地比较它们的所有属性。这包括检查它们的类型、属性数量,以及每个属性的键和值是否都匹配。如果属性值本身又是对象或数组,就需要再次递归地进行比较。

function deepEqual(obj1, obj2) {
    // 1. 快速路径:如果引用相同,那肯定是相等的
    if (obj1 === obj2) return true;

    // 2. 检查基本类型和null:如果不是对象或者其中一个是null,且引用不相同,那就不相等
    // 比如 deepEqual(null, undefined) -> false
    // deepEqual(1, '1') -> false
    if (obj1 === null || typeof obj1 !== 'object' ||
        obj2 === null || typeof obj2 !== 'object') {
        return false;
    }

    // 3. 检查Date和RegExp对象:它们有特定的比较方式
    if (obj1 instanceof Date && obj2 instanceof Date) {
        return obj1.getTime() === obj2.getTime();
    }
    if (obj1 instanceof RegExp && obj2 instanceof RegExp) {
        return obj1.source === obj2.source && obj1.flags === obj2.flags;
    }

    // 4. 检查数组:比较长度和元素
    if (Array.isArray(obj1) && Array.isArray(obj2)) {
        if (obj1.length !== obj2.length) return false;
        for (let i = 0; i < obj1.length; i++) {
            if (!deepEqual(obj1[i], obj2[i])) return false;
        }
        return true;
    }

    // 5. 检查普通对象:比较属性
    const keys1 = Object.keys(obj1);
    const keys2 = Object.keys(obj2);

    if (keys1.length !== keys2.length) return false;

    // 确保obj2包含obj1的所有属性,且对应值相等
    for (const key of keys1) {
        // 使用 hasOwnProperty 确保只比较对象自身的属性,而不是原型链上的
        if (!Object.prototype.hasOwnProperty.call(obj2, key) || !deepEqual(obj1[key], obj2[key])) {
            return false;
        }
    }

    return true;
}

// 示例用法:
const objA = { a: 1, b: { c: 2 } };
const objB = { a: 1, b: { c: 2 } };
const objC = { a: 1, b: { c: 3 } };
const arr1 = [1, { x: 10 }];
const arr2 = [1, { x: 10 }];
const arr3 = [1, { x: 20 }];

console.log('objA vs objB (deepEqual):', deepEqual(objA, objB)); // true
console.log('objA vs objC (deepEqual):', deepEqual(objA, objC)); // false
console.log('arr1 vs arr2 (deepEqual):', deepEqual(arr1, arr2)); // true
console.log('arr1 vs arr3 (deepEqual):', deepEqual(arr1, arr3)); // false
console.log('{a:1} vs {a:1} (===):', {a:1} === {a:1}); // false (引用不同)
登录后复制

为什么JavaScript中直接使用
==
登录后复制
===
登录后复制
无法正确比较对象?

这其实是JavaScript的一个核心概念,也是初学者常常感到困惑的地方。当你在JavaScript中创建一个对象字面量

{}
登录后复制
、一个数组
[]
登录后复制
,或者使用
new Object()
登录后复制
new Array()
登录后复制
等方式时,你实际上是在内存中开辟了一个新的空间来存储这个数据结构。变量名只是这个内存地址的一个“引用”或者说“指针”。

==
登录后复制
(宽松相等)和
===
登录后复制
(严格相等)在比较非基本类型(也就是对象、数组、函数等)时,它们的行为是一致的:它们都只比较这两个变量所指向的内存地址是否相同。

举个例子,你可能会写出这样的代码:

const user1 = { name: 'Alice', age: 30 };
const user2 = { name: 'Alice', age: 30 };
const user3 = user1;

console.log(user1 == user2); // false
console.log(user1 === user2); // false

console.log(user1 == user3); // true
console.log(user1 === user3); // true
登录后复制

你看,

user1
登录后复制
user2
登录后复制
尽管内容一模一样,但因为它们是在不同的时间、不同的语句中创建的,所以在内存中占据了两个不同的位置。它们是两个独立的“对象实例”,所以
==
登录后复制
===
登录后复制
都会返回
false
登录后复制
。而
user3 = user1
登录后复制
则是把
user1
登录后复制
的引用地址直接赋值给了
user3
登录后复制
,所以它们现在都指向了同一个内存地址,自然也就相等了。

这和基本类型(如字符串、数字、布尔值、

null
登录后复制
undefined
登录后复制
Symbol
登录后复制
BigInt
登录后复制
)的处理方式截然不同。基本类型在比较时,是直接比较它们的值。比如
1 == '1'
登录后复制
true
登录后复制
(因为类型转换),
1 === 1
登录后复制
true
登录后复制
。这种设计在某些场景下非常高效,但在需要内容比较时就显得有些“反直觉”了。理解这一点,是掌握JavaScript对象操作的关键一步。

实现一个可靠的深层比较函数,需要考虑哪些关键细节?

自己动手写一个深层比较函数,虽然不总是最佳实践(后面会提到库的优势),但绝对是理解JavaScript对象和递归逻辑的绝佳练习。一个“可靠”的深层比较函数,需要考虑的细节确实不少,它远不止简单地遍历属性那么简单。

首先,类型检查是基石。如果两个待比较的值类型都不同,那它们肯定不相等(除了

null == undefined
登录后复制
这种特例,但在严格比较中依然不相等)。我的函数开头就处理了
null
登录后复制
和非对象类型的情况,这是为了快速排除不匹配的场景。

接着,对特定内置对象的特殊处理。像

Date
登录后复制
对象,它们的值是时间戳,但直接比较对象实例肯定不行,我们需要比较它们的
getTime()
登录后复制
返回的时间戳。
RegExp
登录后复制
(正则表达式)也类似,需要比较它们的
source
登录后复制
flags
登录后复制
。这些都是
typeof
登录后复制
返回
object
登录后复制
但行为特殊的家伙。

然后是数组和普通对象的区分处理。虽然它们都是对象,但数组有

length
登录后复制
属性和索引访问的特性,普通对象则是键值对。我的函数里用
Array.isArray()
登录后复制
来区分,然后对数组逐个元素递归比较,对普通对象则遍历
Object.keys()
登录后复制
得到的属性名,然后递归比较属性值。

通义万相
通义万相

通义万相,一个不断进化的AI艺术创作大模型

通义万相 596
查看详情 通义万相

递归是核心,也是陷阱。当一个对象的属性值又是另一个对象或数组时,我们就需要再次调用

deepEqual
登录后复制
函数。这种递归调用是实现“深层”比较的关键。但递归也会带来一个潜在的问题:循环引用。如果
objA
登录后复制
的一个属性指向
objB
登录后复制
,而
objB
登录后复制
的一个属性又指向
objA
登录后复制
,那么无限递归就会发生,最终导致栈溢出。一个真正健壮的深层比较函数,通常会维护一个“已访问对象”的集合(比如使用
WeakSet
登录后复制
),在递归过程中检查当前对象是否已经被访问过,以此来打破循环。我上面提供的简化版函数并没有处理循环引用,这是自己实现时一个常见的挑战点。

hasOwnProperty
登录后复制
的重要性。在遍历对象属性时,使用
Object.keys()
登录后复制
通常是安全的,因为它只返回对象自身的(可枚举的)属性。但如果需要更细致的控制,或者在某些旧的遍历方式中,确保只比较对象自身的属性而不是原型链上的属性,
Object.prototype.hasOwnProperty.call(obj, key)
登录后复制
是一个好习惯。

最后,还有一些JavaScript的“怪癖”需要考虑,比如

NaN
登录后复制
NaN === NaN
登录后复制
false
登录后复制
),以及
-0
登录后复制
+0
登录后复制
。一个非常严格的深层比较可能需要特殊处理这些情况,但我上面的函数遵循了
===
登录后复制
的行为,即
NaN
登录后复制
不等于
NaN
登录后复制
-0
登录后复制
等于
+0
登录后复制
。这些都是根据具体需求来决定的。

除了手动实现,还有哪些现成的工具或库可以帮助我们进行对象比较?

在实际项目开发中,尤其是在生产环境中,我们很少会自己从头编写一个深层比较函数,原因很简单:自己写的可能不够健壮(比如没处理循环引用、特殊对象类型、性能优化等),而且造轮子也浪费时间。这时候,成熟的第三方库就成了我们的首选。

最常用的两个库,非 LodashRamda 莫属。

  1. Lodash:它提供了非常强大的工具函数集,其中就包括

    _.isEqual()
    登录后复制
    。这个函数功能非常全面,它能正确处理各种JavaScript值类型,包括:

    • 基本类型
    • 普通对象和数组
    • Date
      登录后复制
      RegExp
      登录后复制
      对象
    • 函数(比较引用)
    • NaN
      登录后复制
      Infinity
      登录后复制
      -0
      登录后复制
    • 甚至能够处理循环引用(这是它比许多简易实现强大得多的地方)。

    使用起来非常简单:

    import _ from 'lodash';
    
    const objA = { a: 1, b: { c: 2 } };
    const objB = { a: 1, b: { c: 2 } };
    const circularObj1 = {};
    const circularObj2 = {};
    circularObj1.a = circularObj2;
    circularObj2.a = circularObj1; // 循环引用
    
    console.log(_.isEqual(objA, objB)); // true
    console.log(_.isEqual(circularObj1, circularObj2)); // true (Lodash 完美处理)
    登录后复制
  2. Ramda:这是一个更注重函数式编程的库,它也提供了

    R.equals()
    登录后复制
    R.equals
    登录后复制
    的行为和
    _.isEqual
    登录后复制
    类似,同样能进行深层比较,并且对各种边缘情况和循环引用有很好的支持。

    import * as R from 'ramda';
    
    const objA = { a: 1, b: { c: 2 } };
    const objB = { a: 1, b: { c: 2 } };
    
    console.log(R.equals(objA, objB)); // true
    登录后复制

除了这些功能全面的工具库,有时你可能会看到一些简单的“hack”方法,比如:

  • JSON.stringify()
    登录后复制
    比较
    JSON.stringify(obj1) === JSON.stringify(obj2)
    登录后复制
    。 这种方法虽然代码量少,但非常不推荐用于通用场景。它有几个致命缺陷:
    • 属性顺序敏感
      {a:1, b:2}
      登录后复制
      {b:2, a:1}
      登录后复制
      字符串化后是不同的,但作为对象它们是相等的。
    • 无法处理
      undefined
      登录后复制
      、函数、
      Symbol
      登录后复制
      :这些类型在
      JSON.stringify
      登录后复制
      时会被忽略或转换为
      null
      登录后复制
      ,导致错误判断。
    • 无法处理循环引用:会直接报错。
    • 日期对象会转为字符串:比较的是字符串形式,而不是时间戳。 所以,这种方法只适用于非常简单、属性顺序固定、不含特殊类型的纯数据对象。

选择哪种方式取决于项目的需求和复杂性。对于简单的、一次性的比较,自己写一个精简版或许可以。但对于任何需要健壮性、性能和完整性保证的场景,引入 Lodash 或 Ramda 这样的成熟库,绝对是更明智、更省心的选择。它们经过了大量的测试和优化,能够应对各种复杂的对象结构。

以上就是js怎么判断两个对象是否相等的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号