JavaScript 中包含基础的值类型
,引用类型
及其他类型
。
关于值类型和引用类型的区别,若有不理解的可以看这篇文章补课 【JavaScript】深拷贝与浅拷贝 ,这里就不再赘述。
当要判断 引用类型 时,以对象举例,则需要 遍历其所有的属性 来进行比较,只有属性对应的值完全相等,才能说两个对象相等,这在源码中如何实现呢?
先来看看 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;
代码中所用到的 isArray
、isNumber
等判断基础类型的函数,都在前几期有提到过,还没看过的同学可以来复习一下
【源码阅读 | xe-utils源码 | 01】判断基础类型
【源码阅读 | xe-utils源码 | 02】判断Array类型
【源码阅读 | xe-utils源码 | 03】isPlainObject | isTypeError
【源码阅读 | xe-utils源码 | 04】isEmpty 判断是否空对象
【源码阅读 | xe-utils源码 | 05】判断ES6中的新类型
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 }
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 }
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