写个数组深比对函数?(超详细版)

240 阅读4分钟

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战

前言

大家好,我是coder__chen。在一个平平无奇的下午我准备划完水下班的时候,桌面右下角的微信消息图标突然闪动起来,点开一看原来是朋友发的消息:能不能写一个深对比函数

方法实现

首先依据题意已知要进行比较的数组是一个不定层级,且数组元素类型可以是任意类型,任意深度。如果元素是基本数据类型如NumberStringBooleanSynbolUndefinedNull那直接使用全等运算符进行比较即可。如果元素类型是Function这直接调用函数的toString方法对函数体字符串形式进行比较即可。但是如果元素类型时ArrayObject那就需要进行再次遍历每一个改元素的子元素进行对比,此时聪明的你肯定想到了通过递归函数的来进行比较不就好了。大体思路已经构思好了那就开干吧!

定义类型判断函数

工欲善其事必先利其器,根据刚才的思路先把几个一会需要用到的类型判断函数定义好。

// 判断数据类型是否为对象
const isObject = obj => Object.prototype.toString.call(obj) === '[object Object]'

// 判断数据类型是否为数组
const isArray = arr => Array.isArray(arr)

// 判断数据类型是否为函数
const isFunction = func => typeof func === 'function'

// 判断数据类型是否为日期对象
const isDate = date => Object.prototype.toString.call(date) === '[object Date]'

编写数组深对比函数deepCompareArray

/**
 *数组深度比较函数
 * @param {Array} arr1 待比较数组1
 * @param {Array} arr2 待比较数组2
 * @returns Boolean 比较结果
 */
const deepCompareArray = (arr1, arr2) => {
  // 判断传入的两个参数是否为都为数组,如果有一个不是直接返回false
  if (!isArray(arr1) || !isArray(arr2)) return false
  // 判断传入的两个数组的栈地址值是否相同,相同直接返回true
  if (arr1 === arr2) return true
  // 以上过滤条件都不满足开始循环递归比较
  return compareData(arr1, arr2)
}

编写递归逻辑

/**
 * 比较对象或数组是否相同
 * @param {Array|Object} data1 待比较的对象或数组1
 * @param {Array|Object} data2 待比较的对象或数组2
 * @returns Boolean 比较结果
 */
const compareData = (data1, data2) => {
  // 判断传入数据的类型是否为数组,由于前面逻辑已经确定二者数据类型一致,所以只需知道一个就行
  const isArrayFlag = isArray(data1)
  // 获取data1的元素个数
  const dataLength1 = isArrayFlag ? data1.length : Object.keys(data1).length
  // 获取data2的元素个数
  const dataLength2 = isArrayFlag ? data2.length : Object.keys(data2).length
  // 判断传入的两个数组的长度是否相同,不同直接返回false
  if (dataLength1 !== dataLength2) return false
  // 判断传入的两个数组是否都空数组或空对象,是直接返回true
  if (dataLength1 === 0 && dataLength2) return true
  /**
   * 由于for...in可以遍历数组和对象类型的数据,但会把原型链上的数据也一起遍历出来。
   * 所以将数据进行一次浅拷贝便能去除掉原型链的影响
   */
  const copyData1 = isArrayFlag ? [...data1] : { ...data1 }
  const copyData2 = isArrayFlag ? [...data2] : { ...data2 }
  // 判断标识 默认为true
  let flag = true
  // 通过for...in循环数组或对象 进行对应索引项比较
  for (let i in copyData1) {
    // 对应索引项值不等进入可能值判断
    if (copyData1[i] !== copyData2[i]) {
      switch (true) {
        // 如果二者都是数组或都是普通对象则递归调用compareData再次循环逐个判断
        case (isObject(copyData1[i]) && isObject(copyData2[i])) ||
          (isArray(copyData1[i]) && isArray(copyData2[i])):
          flag = compareData(copyData1[i], copyData2[i])
          break
        // 如果二者都是函数则判断函数体内容是否一致
        case isFunction(copyData1[i]) && isFunction(copyData2[i]):
          flag = copyData1[i].toString() === copyData2[i].toString()
          break
        // 如果二者都是日期对象则判断时间戳是否一致
        case isDate(copyData1[i]) && isDate(copyData2[i]):
          flag = copyData1[i].valueOf() === copyData2[i].valueOf()
          break
        // 不满足以上条件再判断两个比较数是不是都为NaN
        default:
          flag = Number.isNaN(copyData1[i]) && Number.isNaN(copyData2[i])
      }
    }
    // 如果比较标识为false则证明不相同,立即停止循环
    if (!flag) break
  }
  // 返回比较结果
  return flag
}

写完以上逻辑数组的深比对大体就完成了,接下来就可以来测试以下效果了。

// 先定义两个有一定复杂度的数据:
const date1 = new Date('2022-01-20 13:23:42')
const date2 = new Date('2022-01-20 13:23:42')
const list1 = [
  1,
  2,
  3,
  {
    a: 2,
    b: 3,
    d: { a: { e: { g: { b: 2 } } }, y: [{ a: 2 }, { w: 33 }] },
    c: () => 1
  },
  4,
  5,
  6,
  NaN,
  date1
]
const list2 = [
  1,
  2,
  3,
  {
    a: 2,
    b: 3,
    d: { a: { e: { g: { b: 2 } } }, y: [{ a: 2 }, { w: 33 }] },
    c: () => 1
  },
  4,
  5,
  6,
  NaN,
  date2
]

// 使用深比对函数
console.log(deepCompareArray(list1, list2))

运行后输出结果为true image.png

小结

至此数组深比对工具函数逻辑就编写完了,文中主要运用的是递归的思路实现,利用for...in方法即可循环对象又可以循环数组的特性,对于对象和数组类型的元素就可以放在一起统一处理。上述的写法稍微改造改造还能进行对象深比对,感兴趣的同学可以动手改造下试试。

如果觉得本文有帮助 记得点赞三连哦 十分感谢!