Topic Description

Please implement an isEqual function that performs deep comparison. Deep comparison is used to compare whether two different objects have the same values.

const object = { a: 1, b: [2, 3] };
const other = { a: 1, b: [2, 3] };

isEqual(object, other);
// => true

console.log(object === other)
// => false

Answer


function isEqual(value, other) {
  // 先處理兩個值都是原始型別 (primitive type) 的狀況
  if (typeof value !== "object" && typeof other !== "object") {
    // 在 JavaScript 中,NaN 是唯一不等於自己的值,所以要特別處理
    if (Number.isNaN(value) && Number.isNaN(other)) {
      return true;
    }
    return value === other;
  }
  
  // null 的型別是 object,所以我們需要額外處理 null 的狀況
  if (value === null && other === null) {
    return true;
  }
  
  // 假如一個是原始型別,另一個是物件型別,那就回傳 false
  if (typeof value !== typeof other) {
    return false;
  }
  
   // 假如通過以上的條件,代表兩個值都是物件型別,
  // 所以我們先比較兩個物件,如果是來自同個址 (reference) 則回傳 true
  if (value === other) {
    return true;
  }
  
  // 接著,當這兩個物件都是陣列時的狀況
  if (Array.isArray(value) && Array.isArray(other)) {
    // 如果兩個陣列長度不同,則回傳 false
    if (value.length !== other.length) {
      return false;
    }
    // 迭代陣列中的每個值,然後遞迴地用 isEqual 比較兩個值
    for (let i = 0; i < value.length; i++) {
      if (!isEqual(value[i], other[i])) {
        return false;
      }
    }

    return true;
  }

  // 只有一個是陣列,但另一個不適的狀況,則回傳 false
  // 因為上面的 && 沒有成立,代表不會兩個都是陣列,這邊用 || 就能判斷是否有一個是陣列
  if (Array.isArray(value) || Array.isArray(other)) {
    return false;
  }
  
  // 如果上面的條件中都沒有回傳,剩下的可能性是兩個值都是物件
  // 先檢查兩個物件的 key 一樣多,不一樣多代表兩個物件一定不同
  if (Object.keys(value).length !== Object.keys(other).length) {
    return false;
  }

  // 假如兩個物件有一樣多的 key 透過 Object.entires 迭代過第一個物件
  for (const [key, val] of Object.entries(value)) {
    // 如果第一個物件中的某個 key 不存在於第二個物件,代表兩者不同
    if (!(key in other)) {
      return false;
    }

    // 如果第一個物件中的鍵值對與第二個的不同,也代表兩個物件不同
    // 切記,因為值可能也是某個物件,所以要用 isEqual 遞迴地檢查是否兩個值一樣
    if (!isEqual(val, other[key])) {
      return false;
    }
   }
   return true
}