0

0

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

煙雲

煙雲

发布时间:2025-08-17 14:30:02

|

430人浏览过

|

来源于php中文网

原创

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()
得到的属性名,然后递归比较属性值。

Smart Picture
Smart Picture

Smart Picture 智能高效的图片处理工具

下载

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

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获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

554

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

374

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

731

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

477

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

394

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

991

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

656

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

551

2023.09.20

高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

0

2026.01.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 3.7万人学习

Pandas 教程
Pandas 教程

共15课时 | 0.9万人学习

ASP 教程
ASP 教程

共34课时 | 3.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

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