Даны два объекта, определить, равны ли они. Это распространенный вопрос на собеседованиях по Javascript — и не зря. Ответ на этот вопрос требует от вас понимания:

  • Примитивные и непримитивные типы
  • Операторы равенства (т.е. === ) и почему их нельзя использовать для сравнения объектов или массивов
  • Передача по значению против передачи по ссылке
  • Оператор typeof
  • Рекурсивные функции

Предположим, у нас есть два объекта (obj1 и obj2), и мы не знаем их значений. Нам нужно определить, равны ли они. Для примитивных типов, таких как числа, строки и логические значения, мы можем просто использовать оператор строгого равенства (===).

function areDeeplyEqual(obj1, obj2) {
  if (obj1 === obj2) return true;

  
  return false; 
}

Если мы получим два равных примитивных значения, мы можем немедленно вернуть true. Этот условный оператор также будет базовым для нашей рекурсивной функции. (к которому я вернусь позже)

Однако когда оператор строгого равенства используется для сравнения непримитивных типов (например, массивов или объектов), он проверяет, являются ли они одним и тем же объектом в памяти. Следовательно, нам необходимо ввести понятие глубокого равенства. Два массива или два объекта глубоко равны, если все их ключи (или индексы в случае массивов) равны и содержат одинаковые значения. Если они содержат вложенные массивы или объекты, то эти вложенные массивы или объекты также должны быть глубоко равными.

В этот момент мы возвращаемся к нашей функции. Если выполнение кода проходит мимо условного оператора сверху, оператор === определил, что значения не равны, что должно означать одно из следующего:

  • Два значения являются двумя примитивными значениями, но не равными (не равными)
  • Эти два значения представляют собой два объекта (может быть равны, а может и нет)
  • Эти два значения представляют собой два массива (может быть равны, а может и нет)
  • Два значения относятся к разным непримитивным типам данных, т. е. один массив и один объект (не равные).
  • Два значения состоят из одного примитивного типа и одного непримитивного типа, т. е. одной строки и одного массива (не равных).

Как мы видим, единственные два случая, когда значения могут быть равными, — это если они являются двумя непримитивными значениями одного и того же типа (т. е. двумя массивами или двумя объектами). Итак, теперь давайте определим, являются ли два значения двумя массивами. И если это два массива, давайте определим, являются ли эти два массива глубоко равными.

function areDeeplyEqual(obj1, obj2) {
  if (obj1 === obj2) return true;

  if (Array.isArray(obj1) && Array.isArray(obj2)) {
  
      if(obj1.length !== obj2.length) return false;
      
      return obj1.every((elem, index) => {
        return areDeeplyEqual(elem, obj2[index]);
      })
  
  }
  
  return false; 
}

Остановитесь на секунду и проанализируйте приведенный выше код. Если предположить, что obj1 и obj2 не могут быть объектами (только массивами или примитивными типами), то эта функция уже будет работать как задумано. Мы используем Array.isArray(), чтобы определить, что оба объекта являются массивами. Если они не одинаковой длины, мы возвращаем false, потому что знаем, что они не равны. Если они имеют одинаковую длину, мы можем использовать метод every Array, который вернет true, если каждый элемент массива пройдет условную проверку. В обратном вызове, предоставляемом методу every, мы рекурсивно вызываем функцию areDeeplyEqual().

Прежде чем двигаться дальше, давайте подведем итоги того, где мы находимся на данный момент. Помните, мы сказали, что если код не соответствует первому условному оператору выше (if (obj1 === obj2) return true), единственные два случая, в которых два значения могут быть равными, — это если два значения являются двумя массивами или двумя объектами. . Ну, мы только что учли случай, когда оба значения являются массивами.

Итак, на следующем шаге нам нужно определить, являются ли неизвестные значения объектами, но немассивами. Мы можем сделать это с помощью оператора typeof. Оператор typeof вернет “object”, если значение является объектом, массивом или, что интересно, нулем. Да, typeof null в Javascript возвращает “object”. (это ошибка ранних дней Javascript, но нам, тем не менее, нужно это учитывать.

    if(typeof obj1 === "object" && typeof obj2 === "object" && obj1 !== null && obj2 !== null) {
      if(Array.isArray(obj1) || Array.isArray(obj2)) return false;
      
      const keys1 = Object.keys(obj1)
      const keys2 = Object.keys(obj2)
  
      if(keys1.length !== keys2.length || !keys1.every(key => keys2.includes(key))) return false;
        
      for(let key in obj1) {
        console.log(obj1[key], obj2[key])
         let isEqual = areDeeplyEqual(obj1[key], obj2[key])
         if (!isEqual) { return false; }
      }
  
      return true;
    
  }

Эта первая строка проверяет, являются ли obj1 и obj2 объектами и не нулевыми. Следующая строка проверяет, является ли obj1 или obj2 массивом. Если любой из них является массивом, мы возвращаем false. Вот как мы учитываем, есть ли у нас сценарий, в котором одно из неизвестных значений является массивом, а другое — объектом.

В if(keys1.length !== keys2.length || !keys1.every(key => keys2.includes(key))) return false мы проверяем, равно ли количество ключей в obj1 и obj2 и включен ли каждый ключ в obj1 в obj2. Если какое-либо из этих условий не выполняется, функция возвращает false.

Наконец, цикл перебирает каждый ключ в obj1 и рекурсивно проверяет, соответствует ли ему соответствующее свойство в obj2. Если какое-либо свойство не является глубоко равным, функция возвращает false.

В этом сообщении блога мы рассмотрели, как определить, являются ли два объекта глубоко равными в JavaScript. Мы обсудили различия между примитивными и непримитивными типами, особенности оператора равенства (===) и концепцию глубокого равенства. Я предоставил рекурсивную функцию, которая проверяет, являются ли два массива или объекта глубоко равными, сравнивая их ключи/индексы и значения.

Я надеюсь, что этот пост предоставил подробное объяснение темы и полезную функцию для определения глубокого равенства в JavaScript.

Полный код ниже:

function areDeeplyEqual(obj1, obj2) {
  if (obj1 === obj2) return true;

  if (Array.isArray(obj1) && Array.isArray(obj2)) {

    if(obj1.length !== obj2.length) return false;
    
    return obj1.every((elem, index) => {
      return areDeeplyEqual(elem, obj2[index]);
    })


  }

  if(typeof obj1 === "object" && typeof obj2 === "object" && obj1 !== null && obj2 !== null) {
    if(Array.isArray(obj1) || Array.isArray(obj2)) return false;
    
    const keys1 = Object.keys(obj1)
    const keys2 = Object.keys(obj2)

    if(keys1.length !== keys2.length || !keys1.every(key => keys2.includes(key))) return false;
      
    for(let key in obj1) {
      console.log(obj1[key], obj2[key])
       let isEqual = areDeeplyEqual(obj1[key], obj2[key])
       if (!isEqual) { return false; }
    }

    return true;
    
  }

  return false;
}