对象数组深度比较的实现

380 阅读2分钟

需求: 如果两个数组中的元素仅仅是 index 不同, 则认为两个数组是相等的; 如果两个对象中, 它们基本数据类型的数据是相同的, 它们的引用数据类型中 k-v 都是可以一一对应的, 则认为两个对象是相等的.

注意: 只考虑了数据类型中 Null, Undefined, String, Number, Object, Array 的情况, 没有考虑到一些边界情况, 比如 NaN 等, 也没有考虑 Map 和 Set 的数据类型.

对于业务中普通的 Js 对象的判断基本够用

function deepCompare(x, y) {
  const typeNull = '[object Null]'
  const typeUndefined = '[object Undefined]'
  const typeObject = '[object Object]'
  const typeArr = '[object Array]'
  const myTypeof = (v) => Object.prototype.toString.call(v)

  function doCompare(x, y) {
    // console.log('doCompare', x, y)

    /**
     * 判断基本类型
     */
    const typeX = myTypeof(x)
    const typeY = myTypeof(y)
    if (typeX !== typeY) return false
    if ((typeX === typeNull && typeY === typeNull) ||
      (typeX === typeUndefined && typeY === typeUndefined)
    ) {
      return true
    }

    if (isNaN(x) && isNaN(y) && typeof x === 'number' && y === 'number') return true

    if (x === y) return true

    if ((typeof x === 'function' && typeof y === 'function') ||
      (x instanceof Date && y instanceof Date) ||
      (x instanceof RegExp && y instanceof RegExp) ||
      (x instanceof String && y instanceof String) ||
      (x instanceof Number && y instanceof Number)) {
      return x.toString() === y.toString()
    }

    /**
     * 判断数组
     */
    if (Array.isArray(x) || Array.isArray(y)) {
      if (x.length !== y.length) return false
      if (x.length === y.length && x.length === 0) return true

      for (let itemInX of x) {
        const resultList = []
        for (let itemInY of y) {
          const result = doCompare(itemInX, itemInY)
          resultList.push(result)
        }
        if (resultList.every(item => !item)) {
          return false
        }
      }

      for (let itemInY of y) {
        const resultList = []
        for (let itemInX of x) {
          const result = doCompare(itemInX, itemInY)
          resultList.push(result)
        }
        if (resultList.every(item => !item)) {
          return false
        }
      }

      return true
    }


    /**
     * 判断对象
     */
    if (myTypeof(x) === typeObject &&
      myTypeof(y) === typeObject
    ) {
      let k
      const xKeys = Reflect.ownKeys(x)
      const yKeys = Reflect.ownKeys(y)

      if (xKeys.length === yKeys.length && xKeys.length === 0) {
        return true
      }

      if (xKeys.length !== yKeys.length) {
        return false
      }

      // 比较 key
      for (k of xKeys) {
        if (!Reflect.has(y, k)) {
          return false
        }
      }

      for (k of yKeys) {
        if (!Reflect.has(x, k)) {
          return false
        }
      }

      // 比较 value
      for (k of xKeys) {
        switch (typeof x[k]) {
          case 'object':
            if (!doCompare(x[k], y[k])) {
              return false
            }
            break
          default:
            if (x[k] !== y[k]) {
              return false
            }
            break
        }
      }

      return true
    }

    return false
  }

  const result = doCompare(x, y)
  return result
}

const testData1 = {
  name: 'abc',
  childObj: {
    label1: '123',
    arr: [{ name: '123' }, { name: '1234' }, { name: '12345' }]
  },
  childArr: [{ name: '123' }, { name: '123' }, { name: '123456' }, { name: '123' }],
}

const testData2 = {
  name: 'abc',
  childArr: [{ name: '123' }, { name: '123456' }, { name: '123' }, { name: '123' }],
  childObj: {
    label1: '123',
    arr: [{ name: '123' }, { name: '12345' }, { name: '1234' }]
  }
}

const testObj1 = {
  key: 'abc',
  value: {
    key: 'def',
    value: {
      key: 'efg',
      value: 123
    }
  },
  type: null
}

const testObj2 = {
  key: 'abc',
  value: {
    key: 'defg',
    value: {
      key: 'efg',
      value: 123
    }
  },
  type: 0
}

const testObjWithArr1 = {
  key: 'abc',
  value: {
    key: 'def',
    value: {
      key: 'efg',
      value: [{id: '1234', name: 'test2'}, {id: '123', name: 'test'}]
    },
    value2: {
      child: {
        id: 'abc, def',
        value: [{id: '123', name: 'test'}, {id: '1234', name: 'test2'}, {id: '1234', name: 'test2'}]
      }
    }
  },
  type: [null, undefined, 1]
}

const testObjWithArr2 = {
  key: 'abc',
  value: {
    key: 'def',
    value2: {
      child: {
        id: 'abc, def',
        value: [{id: '1234', name: 'test2'}, {id: '123', name: 'test'}, {id: '1234', name: 'test2'}]
      }
    },
    value: {
      key: 'efg',
      value: [{id: '123', name: 'test'}, {id: '1234', name: 'test2'}]
    },
  },
  type: [1, null, undefined]
}

const sampleArr1 = ['1', '2']
const sampleArr2 = ['2', '1']

console.log(deepCompare(testData1, testData2)) // true
console.log(deepCompare(testObj1, testObj2)) // false
console.log(deepCompare(testObjWithArr1, testObjWithArr2)) // true
console.log(deepCompare(sampleArr1, sampleArr2)) // trues