经典面试题-js怎么判断两个对象是相等的

139 阅读5分钟

最近在面试的时候面试官问到一个问题:在开发中你是怎么判断两个对象是相等的?

本文章旨在抛砖引玉,总结一下我能想到的方法,希望大家看完后能点赞、关注、收藏,谢谢大家~~

用===判断

如果你只回答了这个答案的话,估计你这次面试离GG就不远了~~

===对于引用类型的比较,只会去比较地址,而不会比较里面的值。我们都知道对于引用类型,js中的变量只会保存它的地址。

下面这个例子就很清晰了:

const obj1 = { a: 1 };
const obj2 = { a: 1 };
const obj3 = obj1;

console.log(obj1 === obj2); // false
console.log(obj1 === obj3); // true

obj1和obj2虽然我们从上帝视角看,他们两个的值是一样的。但是在代码层面而言,obj2的地址与obj1的地址是不一样的,所以上面的代码返回了false。

JSON.stringify

既然引用类型无法直接判断,那我们就将它转换成基础类型再进行判断。我们可以通过JSON.stringify()实现。JSON.stringify() 是 JavaScript 中的一个内置方法,用于将 JavaScript 对象或值转换为 JSON(JavaScript Object Notation)字符串。

它可以满足我们的一部分需求,可以看下下面的例子

const obj1 = {a:1, b:2};
const obj2 = {a:1, b:2};
const obj3 = {a:2, b:1};

console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // true
console.log(JSON.stringify(obj1) === JSON.stringify(obj3)); // false

很明显相当于===的判断,我们现在进行的是值的判断而不是地址的判断了。

但是通过JSON.stringify()判断就可以满足所有情况了吗?看下下面的例子

const obj1 = {a:1, b:2, c: NaN};
const obj2 = {a:1, b:2, c: null};
const obj3 = {a:1, b:2, c: undefined};
const obj4 = {a:1, b:2 };

console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // true
console.log(JSON.stringify(obj3) === JSON.stringify(obj4)); // true

如果从我们的上帝视角去看,我们很容易发现上面的两个判断都应该返回false才对。但是实际上却返回了true,为什么呢?

其实这与JSON的设计有关,在ECMA的规范中,JSON只支持7种取值objectarraynumberstringtruefalse 和 null。所以不是这7种取值的数据通过JSON.stringify()转换时就会有问题。

总结一下JSON.stringify()存在以下问题:

  • 属性的值为函数或undefined的,转换时会丢失
  • NaN,Infinity转换时会被转换成为null
  • Date,RegExp等JSON不支持的数据类型会转换成字符串

如果面试的时候,能回答出使用JSON.stringify()这种方法判断的缺点的话,基本上可以在面试官心中加一丢丢分了~~

依赖第三方库-如Lodash的isEqual

这个问题我们同样可以依赖第三方库来解决,话说又有什么问题不能通过第三方库来解决呢?

但是虽然是调包侠,但是我们在面试的时候肯定也要简单的介绍一下它的实现方式,不然怎么拉开你与其他竞争者的差距呢?

先来看一下官方的介绍:

image.png

使用方法很简单,传入两个需要比较的参数,然后就会返回是否相等。

那么Lodash是怎么实现的呢?由于源码很长且会重构,这里我就为大家精简一下:

  • isEqual()底层其实是调用了另外一个方法baseIsEqual

  • baseIsEqual会进行以下逻辑

  1. 判断两个对象地址是否一致,如果一致则直接返回true
  2. 判断是否NaN和NaN判断
  3. 调用另外一个方法baseIsEqualDeep

-baseIsEqualDeep

  1. 会对两个参数进行类型判断,如果类型不一致直接返回false
  2. 然后就是策略模式,如果是array则调用equalArrays,如果是对象则调用equalObjects等等
  • equalArrays/ equalObjects

其实每个策略里面的原理都大同小异,就是遍历然后判断值是否相同,里面会有两种情况

  1. 如果是基础类型,直接比较值是否相等
  2. 如果是引用类型,则递归调用对应的判断方法

如果你面试的时候能说出上面的基本思路的话,这个问题应该也算过关了~~

自己实现

其实思路跟上面Lodash的实现思路是基本相同的,但是嘛,面试我们时间有限只能写一个基础版本的

function objIsEqual(obj1, obj2, deep) {
    var d = deep || false,
        result = isEqual(obj1, obj2),
        result2 = result.every(function(val) {
        if (Array.isArray(val)) {
            // 元素为数组
            return val.every(function(val) {
                return val;
            });
        } else {
            // 元素不是数组
            return val;
        }
    });

    function isEqual(obj1, obj2) {
        var key1 = Object.getOwnPropertyNames(obj1),
            key2 = Object.getOwnPropertyNames(obj2),
            arr = [];

        if (key1.length !== key2.length) {
            return false;
        }

        for (var i = 0, l = key1.length; i < l; i++) {
            var name = key1[i],
                value1 = obj1[name],
                value2 = obj2[name];

            if (value1 instanceof Object && value2 instanceof Object) {
                //属性的值为引用类型
                arr.push(arguments.callee(value1, value2));
            } else {
                //属性的值不为引用类型
                arr.push(value1 === value2);
            }
        }
        return arr;
    }


    return d ? result2 : result;
}

module.exports = objIsEqual;

实现思路其实跟上面Lodash的思路是一样的,我们这里用一个arr去接收对象里面每个属性的判断结果,最后去遍历数组,如果数组中的一个结果不为true则直接最终结果为false.

上面的代码固然有问题,但是如果在面试的过程中能写出这样一个方法,这个问题基本可以算是过关了。