1. 背景
JavaScript 中包含基础的值类型
,引用类型
及其他类型
。
关于值类型和引用类型的区别,若有不理解的可以看这篇文章补课 【JavaScript】深拷贝与浅拷贝 ,这里就不再赘述。
当要判断 引用类型 时,以对象举例,则需要 遍历其所有的属性 来进行比较,只有属性对应的值完全相等,才能说两个对象相等,这在源码中如何实现呢?
2. 源码解析
2.1 xe-utils 源码
先来看看 xe-utils
的源码,其中为了可扩展性,增加了 自定义判断函数;
如果你觉得过于复杂,可以直接阅读 2.2
中笔者以相同思路 简化过的代码,其中包含每个步骤的详细注释,可学习到的思路基本一致。
var isNumber = require("./isNumber");
var isArray = require("./isArray");
var isString = require("./isString");
var isRegExp = require("./isRegExp");
var isDate = require("./isDate");
var isBoolean = require("./isBoolean");
var isUndefined = require("./isUndefined");
var keys = require("./keys");
var every = require("./every");
function helperEqualCompare(val1, val2, compare, func, key, obj1, obj2) {
if (val1 === val2) {
return true;
}
if (
val1 &&
val2 &&
!isNumber(val1) &&
!isNumber(val2) &&
!isString(val1) &&
!isString(val2)
) {
if (isRegExp(val1)) {
return compare("" + val1, "" + val2, key, obj1, obj2);
}
if (isDate(val1) || isBoolean(val1)) {
return compare(+val1, +val2, key, obj1, obj2);
} else {
var result, val1Keys, val2Keys;
var isObj1Arr = isArray(val1);
var isObj2Arr = isArray(val2);
if (
isObj1Arr || isObj2Arr
? isObj1Arr && isObj2Arr
: val1.constructor === val2.constructor
) {
val1Keys = keys(val1);
val2Keys = keys(val2);
if (func) {
result = func(val1, val2, key);
}
if (val1Keys.length === val2Keys.length) {
return isUndefined(result)
? every(val1Keys, function (key, index) {
return (
key === val2Keys[index] &&
helperEqualCompare(
val1[key],
val2[val2Keys[index]],
compare,
func,
isObj1Arr || isObj2Arr ? index : key,
val1,
val2
)
);
})
: !!result;
}
return false;
}
}
}
return compare(val1, val2, key, obj1, obj2);
}
module.exports = helperEqualCompare;
2.2 简化过后的代码
代码中所用到的 isArray
、isNumber
等判断基础类型的函数,都在前几期有提到过,还没看过的同学可以来复习一下
【源码阅读 | xe-utils源码 | 01】判断基础类型
【源码阅读 | xe-utils源码 | 02】判断Array类型
【源码阅读 | xe-utils源码 | 03】isPlainObject | isTypeError
【源码阅读 | xe-utils源码 | 04】isEmpty 判断是否空对象
【源码阅读 | xe-utils源码 | 05】判断ES6中的新类型
2.2.1 有注释版本
const isArray = require('./isArray')
const isNumber = require('./isNumber')
const isRegExp = require('./isRegExp')
const isDate = require('./isDate')
const isBoolean = require('./isBoolean')
const isString = require('./isString')
function isEqual(o1, o2) {
// 1.值类型比较:直接比较即可 [String | Number | Boolean | null | undefined]
if (o1 === o2) return true
// 2.正则:需将正则转为 string 再进行比较
if (isRegExp(o1)) {
return '' + o1 === '' + o2
}
// 3.日期和布尔
if (isDate(o1) || isBoolean(o1)) {
// + 号的作用:布尔能转为0和1;日期能转为时间戳;转换后直接通过数字型进行比较
// 布尔:o1:true -> +o1:1
// 日期:o1:Sun Jan 23 2022 10:34:09 GMT+0800 (中国标准时间) -> +o1:1642905249716
return +o1 === +o2
}
// 4.引用类型比较
if (
o1 &&
o2 &&
!isNumber(o1) &&
!isNumber(o2) &&
!isString(o1) &&
!isString(o2)
) {
// 4.1 数组及其他类型
const isO1Arr = isArray(o1)
const isO2Arr = isArray(o2)
// 若有一个为数组,则判断两个对象是否都为数组;若都不为数组,则判断对象实例是否属于同一个父类
// 都为同一个类型后再进行深度比较,否则直接返回 false
if (
isO1Arr || isO2Arr
? isO1Arr && isO2Arr
: o1.constructor === o2.constructor
) {
let isE = true
for (let key in o1) {
// 若key非自身属性,则说明key属性原型链上,而非该实例上的属性,因此不相等
if (!o2.hasOwnProperty(key)) {
return false
}
// 重点!通过递归调用,不断深入进行比较
isE = isEqual(o1[key], o2[key])
// 一旦不相等,直接退出循环
if (!isE) return isE
}
return isE
} else {
// 两个实例的类型不一样,直接返回false
return false
}
}
// 其他不符合的情况,如 [只传一个参数 | 两个不相等的String类型 ...]
return false
}
2.2.2 无注释纯净版
const isArray = require('./isArray')
const isNumber = require('./isNumber')
const isRegExp = require('./isRegExp')
const isDate = require('./isDate')
const isBoolean = require('./isBoolean')
const isString = require('./isString')
function isEqual(o1, o2) {
if (o1 === o2) return true
if (isRegExp(o1)) {
return '' + o1 === '' + o2
}
if (isDate(o1) || isBoolean(o1)) {
return +o1 === +o2
}
if (
o1 &&
o2 &&
!isNumber(o1) &&
!isNumber(o2) &&
!isString(o1) &&
!isString(o2)
) {
const isO1Arr = isArray(o1)
const isO2Arr = isArray(o2)
if (
isO1Arr || isO2Arr
? isO1Arr && isO2Arr
: o1.constructor === o2.constructor
) {
let isE = true
for (let key in o1) {
if (!o2.hasOwnProperty(key)) {
return false
}
isE = isEqual(o1[key], o2[key])
if (!isE) return isE
}
return isE
} else {
return false
}
}
return false
}
2.3 最终效果
console.log(isEqual({}, [])) // false
console.log(isEqual({ 0: 1 }, [1])) // false
console.log(isEqual(true, false)) // false
console.log(isEqual({ name: 'test1' }, { name: 'test1' })) // true
console.log(
isEqual(
{ name: 'test1', list: [11, /\d/] },
{ name: 'test1', list: [11, /\d/] }
)
) // true
console.log(
isEqual(
{ name: 'test1', list: [11, 33, { a: /\D/ }] },
{ name: 'test1', list: [11, 33, { a: /\d/ }] }
)
) // false
console.log(isEqual(new Date('2020-01-01'), new Date('2020-01-05'))) // false