最近在面试的时候面试官问到一个问题:在开发中你是怎么判断两个对象是相等的?
本文章旨在抛砖引玉,总结一下我能想到的方法,希望大家看完后能点赞、关注、收藏,谢谢大家~~
用===判断
如果你只回答了这个答案的话,估计你这次面试离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种取值object、array、number、string、true、false 和 null。所以不是这7种取值的数据通过JSON.stringify()转换时就会有问题。
总结一下JSON.stringify()存在以下问题:
- 属性的值为函数或undefined的,转换时会丢失
- NaN,Infinity转换时会被转换成为null
- Date,RegExp等JSON不支持的数据类型会转换成字符串
如果面试的时候,能回答出使用JSON.stringify()这种方法判断的缺点的话,基本上可以在面试官心中加一丢丢分了~~
依赖第三方库-如Lodash的isEqual
这个问题我们同样可以依赖第三方库来解决,话说又有什么问题不能通过第三方库来解决呢?
但是虽然是调包侠,但是我们在面试的时候肯定也要简单的介绍一下它的实现方式,不然怎么拉开你与其他竞争者的差距呢?
先来看一下官方的介绍:
使用方法很简单,传入两个需要比较的参数,然后就会返回是否相等。
那么Lodash是怎么实现的呢?由于源码很长且会重构,这里我就为大家精简一下:
-
isEqual()底层其实是调用了另外一个方法baseIsEqual -
baseIsEqual会进行以下逻辑
- 判断两个对象地址是否一致,如果一致则直接返回true
- 判断是否NaN和NaN判断
- 调用另外一个方法
baseIsEqualDeep
-baseIsEqualDeep
- 会对两个参数进行类型判断,如果类型不一致直接返回false
- 然后就是策略模式,如果是array则调用
equalArrays,如果是对象则调用equalObjects等等
equalArrays/equalObjects
其实每个策略里面的原理都大同小异,就是遍历然后判断值是否相同,里面会有两种情况
- 如果是基础类型,直接比较值是否相等
- 如果是引用类型,则递归调用对应的判断方法
如果你面试的时候能说出上面的基本思路的话,这个问题应该也算过关了~~
自己实现
其实思路跟上面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.
上面的代码固然有问题,但是如果在面试的过程中能写出这样一个方法,这个问题基本可以算是过关了。