手写深度比较,模拟 lodash isEqual

2,066 阅读2分钟

参考:lodash.isEqual | Lodash 中文文档 | Lodash 中文网 (lodashjs.com)

比较对象的键值对,都一样则为true,反之false; 若对象的属性值是引用类型,比较的是实际值不是地址(当然,若地址是一样的,那么肯定相等,return true);

思路:

默认不会比较函数
1.不是对象,直接返回比较结果,return ===
2. 地址相等的对象 return true
3. 两个都是对象||数组
(1)比较两对象keys的个数
(2)以obj1的key为基准,和obj2依次递归比较

function isObject(obj) {
    return typeof obj === 'object' && obj !== null
}
// 全相等(深度)
function isEqual(obj1, obj2) {
    if (!isObject(obj1) || !isObject(obj2)) {
        // 值类型(注意,参与 equal 的一般不会是函数)
        return obj1 === obj2
    }
    if (obj1 === obj2) {
        return true
    }
    // 两个都是对象或数组,而且不相等
    // 1. 先取出 obj1 和 obj2 的 keys ,比较个数
    const obj1Keys = Object.keys(obj1)
    const obj2Keys = Object.keys(obj2)
    if (obj1Keys.length !== obj2Keys.length) {
        return false
    }
    // 2. 以 obj1 为基准,和 obj2 一次递归比较
    for (let key in obj1) {
        // 比较当前 key 的 val —— 递归!!!
        const res = isEqual(obj1[key], obj2[key])
        if (!res) {
            return false
        }
    }
    // 3. 全相等
    return true
}

以上代码存在部分bug:

  • 循环引用问题

  • 没有考虑Symbol作为key的情况。 这里只要使用Object.getOwnPropertySymbols()获取Symbol keys即可

  • 有着相同value的不同类型入参会被错误判断成相等的问题。 使用Object.prototype.toString.call(a) === Object.prototype.toString.call(b) 验证一下入参类型

      ```const a = [1, 2, 3]
      const b = {0: 1, 1: 2, 2:3}
      isEqual(a, b) // 理应是false,但会得到true
      ```
    
  • 对于 value 为 undefined 但 key 不同的情况的错误判断

 * @param {any} a
 * @param {any} b
 * @return {boolean}
 */
const map = new Map()  // (改进1)
function isEqual(a, b) {
  // primitive Types
  if (!isObject(a) || !isObject(b)) {
    return a === b
  }
  // is same referance?
  if (a === b) {
    return true
  }
  // Optional: for different types but same primitive value
  // (改进3)
  if (!isSameType(a, b)) { 
    return false
  }
  // support circular reference in array and object
  if (map.has(a) || map.has(b)) {
    return true
  }
  map.set(a, b)
  // support symbol as keys
  // (改进2)
  const aKeys = [...Object.keys(a), ...Object.getOwnPropertySymbols(a)]
  const bKeys = [...Object.keys(b), ...Object.getOwnPropertySymbols(b)]
  if (aKeys.length !== bKeys.length) {
    return false
  }

  for (let i = 0; i < aKeys.length; i++) {
	 // (改进4)
    if (aKeys[i] !== bKeys[i]) return false
    const res = isEqual(a[aKeys[i]], b[bKeys[i]])
    if (!res) {
      return false
    }
  }
  return true
}

function isObject(obj) {
  return typeof obj === 'object' && obj !== null
}

function isSameType(a, b) {
  return Object.prototype.toString.call(a) === Object.prototype.toString.call(b)
}