面试官:手撕代码!判断两个对象是否相等?

简介: 前言在实际项目开发中,判断两个对象是否相等可能是比较常见的需求了,有些小伙伴会使用第三方库实现,有些小伙伴会自己手动实现。不管怎么实现,只能能满足项目需求,那就是好样的。但是可能有些小伙伴如果对 JS 还不够熟悉,他可能就会有疑问:判断相等不是用==比较就可以了吗?答案肯定是错误的,面试官要是听了你这个回答,估计会当场吐血!今天就来学一学如何比较两个对象是否相等?学习目标:实现判断两个对象是否相等,即所有键值对相等。

1.使用===来比较?


很多初学者可能第一个想到的就是这种方式。但是我们需要考虑到 object 是引用类型,我们使用===比较时,实际上是在比较对象的引用地址(内存地址),所以即使两个对象键和值都一样,那么它们的引用地址可能不是一样的。


而我们的值类型就可以使用===比较,因为它们之间的比较就确确实实是通过我们看得见的值比较的。


示例代码:

<script>
  let obj1 = {
    name: "小猪课堂",
    age: 26,
    sex: "不知道"
  }
  let obj2 = {
    name: "小猪课堂",
    age: 26,
    sex: "不知道"
  }
  console.info("通过===比较两个对象", obj1 === obj2); // false
</script>


上段代码中声明了两个对象 obj1 和 obj2,它们的引用地址是不一样的,虽然它们的键值完全相等,但是最终的比较结果还是 false。


我们将两个对象的引用地址改为一样再来试试。


示例代码:

<script>
  let obj1 = {
    name: "小猪课堂",
    age: 26,
    sex: "不知道"
  };
  let obj2 = obj1;
  console.info("通过===比较两个对象", obj1 === obj2); // true
</script>


上段代码两个对象的引用地址是一样的,所以使用===比较结果为 true。


综上所述:

使用===判断两个对象是否相等时只能判断引用地址是否相等,无法达到我们的目标。


补充:

我们使用 Object.is(obj1,obj2)方法判断两个对象相等时也是判断的引用地址是否相等。


2.另辟蹊径


既然使用===和 Object.is()的方法不能实现我们的目标,那我们只有另辟蹊径了。我们都知道如果是值类型的数据,那么可以使用===来进行判断是否相等,依照此思路,那么我们可以循环我们的对象,依次比较对象中的每个键值对就行了。


我们封装一个 isEqual()方法,传入两个值,必须是对象类型,专门用来判断对象是否相等。


先来熟悉几个 API:

  • Object.prototype.toString.call()

用来判断数据类型,返回的是一个字符串,包含类型,如"[object Object]"等等


  • Object.keys()

以一个数组的形式返回对象的键。

  • hasOwnProperty()

检测一个属性是否是对象的自有属性.


示例代码:

<script>
  function isEqual(obj1, obj2) {
    // 判断两个变量是否为对象类型
    let isObj = (toString.call(obj1) === '[object Object]' && toString.call(obj2) === '[object Object]');
    if (!isObj) {
      return false;
    }
    // 判断两个对象的长度是否相等,不相等则直接返回 fase
    let obj1Keys = Object.keys(obj1);
    let obj2Keys = Object.keys(obj2);
    if (Object.keys(obj1).length !== Object.keys(obj2).length) {
      return false;
    }
    // 判断两个对象的每个属性值是否相等
    for (const key in obj1) {
      // 判断两个对象的键是否相等
      if (obj2.hasOwnProperty.call(obj2, key)) {
        let obj1Type = toString.call(obj1[key]);
        let obj2Type = toString.call(obj2[key]);
        // 如果值是对象,则递归
        if(obj1Type === '[object Object]' || obj2Type === '[object Object]') {
          if(!isEqual(obj1[key], obj2[key])) {
            return false;
          }
        } else if (obj1[key] !== obj2[key]) {
          return false; // 如果不是对象,则判断值是否相等
        }
      } else {
        return false;
      }
    }
    return true; // 上面条件都通过,则返回 true
  }
  // 测试用例-1
  let obj1 = {
    name: "小猪课堂",
    age: 26,
    sex: "不知道"
  }
  let obj2 = {
    name: "小猪课堂",
    age: 26,
    sex: "不知道"
  }
  console.info("obj1 === obj2",isEqual(obj1, obj2)); // true
  // 测试用例-2
  let obj3 = {
    name: "小猪课堂",
    age: 26,
    sex: "不知道"
  }
  let obj4 = obj3;
  console.info("obj3 === obj4",isEqual(obj3, obj4)); // true
  obj4.num = 100;
  console.info("obj3 === obj4",isEqual(obj3, obj4)); // true
  // 测试用例-3
  let obj5 = {
    name: "小猪课堂",
    age: 26,
    sex: {
      type: 1
    }
  }
  let obj6 = {
    name: "小猪课堂",
    age: 26,
    sex: {
      type: 1
    }
  }
  console.info("obj5 === obj6",isEqual(obj5, obj6)); // true
</script>


输出结果:73.png


上段代码中我们定义了一个函数用来判断两个对象是否相等,如果传入的参数不是对象,则直接返回 false,如果两个对象的键长度不相等,则直接返回 false,如果两个对象的键对应的值是对象,则使用递归。


上面函数基本上实现了判断两个对象是否相等,但是还是存在一些不足:

  • 如果对象中某个键对应的值是数组类型、Data 类型、Set 类型等等,暂未处理。
  • 对于某些属性值是 null 或者 undefined 还未做处理。


总体而言:上面的函数基本上能够满足我们常见的需求。


总结


判断对象是否相等相对于判断两个值是否相等要复杂一些,但是其中的基本原理还是比较简单的。我们需要的是理解底层原理,其实主要就是对象中属性值类型的判断。衍生出来的是我们需要了解各个 API 的使用,如 toString.call(obj1)或者 hasOwnProperty 等等,中间就是要知道如何判断一个变量的类型,是 Object 还是 Array,还是 Set、Map、Date 等等。



最后:


最推荐推荐大家使用的还是第三方库,特别是在实际项目中,还是建议使用一些完善的第三方库来实现,毕竟它们将各个方面都考虑到了。比如: lodash


想要视频学习,可以移步B站:小猪课堂

相关文章
|
3月前
|
Java 编译器 C++
【Java基础面试一】、为什么Java代码可以实现一次编写、到处运行?
这篇文章解释了Java能够实现“一次编写,到处运行”的原因,主要归功于Java虚拟机(JVM),它能够在不同平台上将Java源代码编译成的字节码转换成对应平台的机器码,实现跨平台运行。
【Java基础面试一】、为什么Java代码可以实现一次编写、到处运行?
|
4月前
|
存储 缓存 监控
Java面试题:在Java中,对象何时可以被垃圾回收?编程中,如何更好地做好垃圾回收处理?
Java面试题:在Java中,对象何时可以被垃圾回收?编程中,如何更好地做好垃圾回收处理?
68 0
|
3月前
|
存储 缓存 Java
面试问Spring循环依赖?今天通过代码调试让你记住
该文章讨论了Spring框架中循环依赖的概念,并通过代码示例帮助读者理解这一概念。
面试问Spring循环依赖?今天通过代码调试让你记住
|
3月前
|
JavaScript
【Vue面试题九】、Vue中给对象添加新属性界面不刷新?
这篇文章讨论了Vue中给对象动态添加新属性时界面不刷新的问题,并提供了三种解决方案:使用`Vue.set()`方法来确保新属性是响应式的并触发视图更新,使用`Object.assign()`创建新对象以合并新属性,以及作为最后手段的`$forceUpdate()`进行强制刷新。文章还简要分析了Vue 2和Vue 3在数据响应式实现上的差异。
|
3月前
|
JavaScript
【Vue面试题八】、为什么data属性是一个函数而不是一个对象?
这篇文章解释了为什么在Vue中组件的`data`属性必须是一个函数而不是一个对象。原因在于组件可能会有多个实例,如果`data`是一个对象,那么这些实例将会共享同一个`data`对象,导致数据污染。而当`data`是一个函数时,每次创建组件实例都会返回一个新的`data`对象,从而确保了数据的隔离。文章通过示例和源码分析,展示了Vue初始化`data`的过程和组件选项合并的原理,最终得出结论:根实例的`data`可以是对象或函数,而组件实例的`data`必须为函数。
【Vue面试题八】、为什么data属性是一个函数而不是一个对象?
|
3月前
|
JavaScript 前端开发 程序员
JS小白请看!一招让你的面试成功率大大提高——规范代码
JS小白请看!一招让你的面试成功率大大提高——规范代码
|
4月前
|
存储 缓存 算法
Java面试题:给出代码优化的常见策略,如减少对象创建、使用缓存等。
Java面试题:给出代码优化的常见策略,如减少对象创建、使用缓存等。
61 0
|
4月前
|
设计模式 存储 缓存
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
55 0
|
5月前
|
存储 算法 Java
面试高频算法题汇总「图文解析 + 教学视频 + 范例代码」之 二分 + 哈希表 + 堆 + 优先队列 合集
面试高频算法题汇总「图文解析 + 教学视频 + 范例代码」之 二分 + 哈希表 + 堆 + 优先队列 合集
|
6月前
|
缓存 监控 算法
Python性能优化面试:代码级、架构级与系统级优化
【4月更文挑战第19天】本文探讨了Python性能优化面试的重点,包括代码级、架构级和系统级优化。代码级优化涉及时间复杂度、空间复杂度分析,使用内置数据结构和性能分析工具。易错点包括过度优化和滥用全局变量。架构级优化关注异步编程、缓存策略和分布式系统,强调合理利用异步和缓存。系统级优化则涵盖操作系统原理、Python虚拟机优化和服务器调优,需注意监控系统资源和使用编译器加速。面试者应全面理解这些层面,以提高程序性能和面试竞争力。
81 1
Python性能优化面试:代码级、架构级与系统级优化