Сравнение Javascript - deepEqual

Вопрос (от Eloquent Javascript 2nd Edition, глава 4, упражнение 4):

Напишите функцию deepEqual, которая принимает два значения и возвращает true, только если они     являются одним и тем же значением или являются объектами с теми же свойствами, значения которых также     равный по сравнению с рекурсивным вызовом deepEqual.

Тестовые случаи:

var obj = {here: {is: "an"}, object: 2};
console.log(deepEqual(obj, obj));
// → true
console.log(deepEqual(obj, {here: 1, object: 2}));
// → false
console.log(deepEqual(obj, {here: {is: "an"}, object: 2}));
// → true

Мой код:

var deepEqual = function (x, y) {
  if ((typeof x == "object" && x != null) && (typeof y == "object" && y != null)) {
    if (Object.keys(x).length != Object.keys(y).length)
      return false;
    for (var prop in x) {
      if (y.hasOwnProperty(prop))
        return deepEqual(x[prop], y[prop]);
    /*This is most likely where my error is. The question states that all the values
    should be checked via recursion; however, with the current setup, only the first
    set of properties will be checked. It passes the test cases, but I would like
    to solve the problem correctly!*/
      }
    }
  else if (x !== y)
    return false;
  else
    return true;
}

Я думаю, что у меня есть общая идея; однако, как я уже сказал в комментарии, программа не проверит второе свойство в объектах. Я чувствую, что у меня есть структурно-логическая проблема, и я просто использую рекурсию не так, как я изначально собирался перебирать свойства, использовать рекурсию для сравнения значений первого свойства, а затем продолжить в цикле до следующего собственности и сравнить снова. Хотя, я не уверен, что это возможно?

Я дал много размышлений и попробовал несколько разных подходов, но это был самый правильный ответ, который я до сих пор пришел. Любые возможные подсказки, чтобы указать мне в правильном направлении?

Ответ 1

Как вы подозреваете, вы возвращаете совпадение первого свойства. Вы должны вернуть false, если это свойство не соответствует, но продолжать искать иначе.

Кроме того, верните false, если нет свойства prop, найденного на y (то есть совпадение совпадений, но не фактические свойства).

Если все свойства совпали, верните true:

var deepEqual = function (x, y) {
  if (x === y) {
    return true;
  }
  else if ((typeof x == "object" && x != null) && (typeof y == "object" && y != null)) {
    if (Object.keys(x).length != Object.keys(y).length)
      return false;

    for (var prop in x) {
      if (y.hasOwnProperty(prop))
      {  
        if (! deepEqual(x[prop], y[prop]))
          return false;
      }
      else
        return false;
    }

    return true;
  }
  else 
    return false;
}

var deepEqual = function (x, y) {
  if (x === y) {
    return true;
  }
  else if ((typeof x == "object" && x != null) && (typeof y == "object" && y != null)) {
    if (Object.keys(x).length != Object.keys(y).length)
      return false;

    for (var prop in x) {
      if (y.hasOwnProperty(prop))
      {  
        if (! deepEqual(x[prop], y[prop]))
          return false;
      }
      else
        return false;
    }

    return true;
  }
  else 
    return false;
}

var obj = {here: {is: "an", other: "3"}, object: 2};
console.log(deepEqual(obj, obj));
// → true
console.log(deepEqual(obj, {here: 1, object: 2}));
// → false
console.log(deepEqual(obj, {here: {is: "an"}, object: 2}));
// → false
console.log(deepEqual(obj, {here: {is: "an", other: "2"}, object: 2}));
// → false
console.log(deepEqual(obj, {here: {is: "an", other: "3"}, object: 2}));
// → true

Ответ 2

Почувствуйте, что эта версия немного более читабельна (легче понять). Логика очень похожа на верхний ответ, хотя. (ES6 на этот раз)

function deepEqual(obj1, obj2) {

    if(obj1 === obj2) // it just the same object. No need to compare.
        return true;

    if(isPrimitive(obj1) && isPrimitive(obj2)) // compare primitives
        return obj1 === obj2;

    if(Object.keys(obj1).length !== Object.keys(obj2).length)
        return false;

    // compare objects with same number of keys
    for(let key in obj1)
    {
        if(!(key in obj2)) return false; //other object doesn't have this prop
        if(!deepEqual(obj1[key], obj2[key])) return false;
    }

    return true;
}

//check if value is primitive
function isPrimitive(obj)
{
    return (obj !== Object(obj));
}

Кстати, есть читерская версия глубокого равенства, которая работает как шарм)) Однако она примерно в 1,6 раза медленнее.

Как заметил ноль298, этот подход чувствителен к упорядочению свойств и не должен восприниматься всерьез

function cheatDeepEqual(obj1, obj2)
{
    return JSON.stringify(obj1) === JSON.stringify(obj2);
}

Ответ 3

Вы можете использовать переменную вне цикла for, чтобы отслеживать сравнение:

var allPropertiesEqual = true;
for (var prop in x) {
    if (y.hasOwnProperty(prop)) {
        allPropertiesEqual = deepEqual(x[prop], y[prop]) && allPropertiesEqual;
    } else {
        allPropertiesEqual = false;
    }
}
return allPropertiesEqual;

Предыдущий пример не оптимизирован специально. Поскольку вы сравниваете объекты, вы знаете, что можете return false, как только найдете неравенство, и вы можете продолжать цикл, пока все предыдущие проверенные свойства равны:

for (var prop in x) {
    if (y.hasOwnProperty(prop)) {
        if (! deepEqual(x[prop], y[prop]) )
            return false; //first inequality found, return false
    } else {
        return false; //different properties, so inequality, so return false
    }
}
return true;

Ответ 4

Я новичок в JS, но так я решил это:

function deepEqual(obj1, obj2) {
if (typeof obj1 === "object" && typeof obj2 === "object") {
    let isObjectMatch = false;
    for (let property1 in obj1) {
        let isPropertyMatch = false;
        for (let property2 in obj2) {
            if (property1 === property2) {
                isPropertyMatch = deepEqual(obj1[property1], obj2[property2])
            }

            if(isPropertyMatch){
                break;
            }
        }

        isObjectMatch  = isPropertyMatch;

        if (!isObjectMatch) {
            break;
        }
    }

    return isObjectMatch;
} else {
    return obj1 === obj2;
}
}

И вот мои тесты:

var obj = {here: {is: "an"}, object: 2};
console.log(deepEqual(obj, obj));
// → true
console.log(deepEqual(obj, {here: 1, object: 2}));
// → false
console.log(deepEqual(obj, {here: {is: "an"}, object: 2}))
// → true
console.log(deepEqual(obj, {object: 2, here: {is: "an"}}));
// → true
console.log(deepEqual(obj, {object: 1, here: {is: "an"}}));
// → false
console.log(deepEqual(obj, {objectt: 2, here: {is: "an"}}));
// → false
console.log(deepEqual(2, 2));
// → true
console.log(deepEqual(2, 3));
// → false
console.log(deepEqual(2, null));
// → false
console.log(deepEqual(null, null));
// → false
console.log(deepEqual(obj, null));
// → false

Ответ 5

Хотя это более многословно, возможно, этот вариант легче читать:

function deepEqual(elem1, elem2) {
    if(elem1 === elem2) {
        return true;
    }
    if(typeof elem1 == 'object' && typeof elem2 == 'object' && elem1 != null && elem2 != null) {
      if(Object.keys(elem1).length == Object.keys(elem2).length) {
          for(let key of Object.keys(elem1)) {
              if(elem2.hasOwnProperty(key) != true) {
                  return false;
              }
          }
          for(let key of Object.keys(elem1)) {
              if(typeof elem1[key] == 'object' && typeof elem2[key] == 'object' && typeof elem1[key] != null && typeof elem2[key] != null) {
                  return deepEqual(elem1[key], elem2[key]);
              }
              else {
                if(elem1[key] !== elem2[key]) {
                    return false;
                }
              }
          } else {
            return false;
          }
        }
      }
    else {
        return false;
    }
    return true;
  }

Ответ 6

<script>
var cmp = function(element, target){

   if(typeof element !== typeof target)
   {
      return false;
   }
   else if(typeof element === "object" && (!target || !element))
   {
      return target === element;
   }
   else if(typeof element === "object")
   {
       var keys_element = Object.keys(element);
       var keys_target  = Object.keys(target);
       
       if(keys_element.length !== keys_target.length)
       {
           return false;
       }
       else
       {
           for(var i = 0; i < keys_element.length; i++)
           {
                if(keys_element[i] !== keys_target[i])
                    return false;
                if(!cmp(element[keys_element[i]], target[keys_target[i]]))
                    return false;
           }
		   return true;
       }
   }
   else
   {
   	   return element === target;

   }
};

console.log(cmp({
    key1: 3,
    key2: "string",
    key3: [4, "45", {key4: [5, "6", false, null, {v:1}]}]
}, {
    key1: 3,
    key2: "string",
    key3: [4, "45", {key4: [5, "6", false, null, {v:1}]}]
})); // true

console.log(cmp({
    key1: 3,
    key2: "string",
    key3: [4, "45", {key4: [5, "6", false, null, {v:1}]}]
}, {
    key1: 3,
    key2: "string",
    key3: [4, "45", {key4: [5, "6", undefined, null, {v:1}]}]
})); // false
</script>

Ответ 7

Основываясь на принятом ответе Пола Руба, я нуждался в том, чтобы он также совпадал со значениями функций, и я хотел, чтобы он был намного более кратким, поэтому я реорганизовал его.

function deepEqual(x, y, z) {
  return x === y || typeof x == "function" && y && x.toString() == y.toString()
    || x && y && typeof x == "object" && x.constructor == y.constructor
    && (z = Object.keys(y)) && z.length == Object.keys(x).length
    && !z.find(v => !deepEqual(x[v], y[v]));
}

var myFunc = (x) => { return x*2; }
var obj = {here: {is: "an", other: "3"}, object: 2, andFunc: myFunc};
console.log(deepEqual(obj, obj));
// → true
console.log(deepEqual(obj, {here: 1, object: 2, andFunc: myFunc}));
// → false
console.log(deepEqual(obj, {here: {is: "an"}, object: 2, andFunc: myFunc}));
// → false
console.log(deepEqual(obj, {here: {is: "an", other: "2"}, object: 2, andFunc: myFunc}));
// → false
console.log(deepEqual(obj, {here: {is: "an", other: "3"}, object: 2, andFunc: myFunc}));
// → true
console.log(deepEqual(obj, {here: {is: "an", other: "3"}, object: 2, andFunc: (x) => { return x*2; }}));
// → true
console.log(deepEqual(obj, {here: {is: "an", other: "3"}, object: 2, andFunc: (x) => { return x*999; }}));
// → false

Ответ 8

Я просто прошел эту главу и хотел показать свою работу тоже.

Недостаток в моей (дайте мне знать, если есть больше) заключается в том, что свойства объекта должны быть в точном порядке. Я предпочитаю решение @paul и @danni.

// Deep equal 
const deepEqual = (x, y) => {
  const xType = typeof x;
  const yType = typeof y; 
  
  if ( xType === 'object' && yType === 'object' && ( x !== null && y !== null ) ) {
    const xKeys = Object.keys(x);
    const yKeys = Object.keys(y);
    const xValues = Object.values(x);
    const yValues = Object.values(y);  
    
    // check length of both arrays
    if ( xKeys.length !== yKeys.length ) return false;
    
    // compare keys
    for ( i = 0; i < xKeys.length; i++ )
      if (xKeys[i] !== yKeys[i]) return false;
      
    // compare values
    for ( i = 0; i < xValues.length; i++ )
      if (!deepEqual(xValues[i], yValues[i])) return false;
      
  } else {
    if ( x !== y ) return false;
  }
  return true;
};

// Objects
let obj1 = {
  value: false,
  pets: null
};

let obj2 = {
  value: false,
  pets: null
};


let obj3 = {
  value: false,
  pets: {
    cat: false,
    dog: {
      better: 'yes'
    }
  }
};

let obj4 = {
  value: false,
  pets: { 
    cat: false,
    dog: {
      better: 'yes'
    }
  }
};


let obj5 = {
  value: false,
  dog: true
};

let obj6 = {
  value: false,
  cat: true
};


let obj7 = {
  value: true,
  dog: {
    cat: {
      wow: true
    }
  }
};

let obj8 = {
  value: true,
  dog: {
    cat: {
      wow: false
    }
  }
};


let obj9 = {
  value: true,
  dog: {
    cat: {
      wow: true
    }
  }
};

let obj10 = {
  dog: {
    cat: {
      wow: true
    }
  },
  value: true
};

// Just for building a pretty result, ignore if you'd like
const result = (x, y) => {
  return `For: <br/>
          ${JSON.stringify(x)} <br/>
          and <br/>
          ${JSON.stringify(y)} <br/>
          <span>>> ${deepEqual(x, y)}</span>`;
};

// To print results in
const resultDivs = document.querySelectorAll('.result');

resultDivs[0].innerHTML = result(obj1, obj2);
resultDivs[1].innerHTML = result(obj3, obj4);
resultDivs[2].innerHTML = result(obj5, obj6);
resultDivs[3].innerHTML = result(obj7, obj8);
resultDivs[4].innerHTML = result(obj9, obj10);
body {
  font-family: monospace;
}

span {
  color: #a0a0a0;
}

.result {
  margin-bottom: 1em;
}
<div class="result">
</div>

<div class="result">
</div>

<div class="result">
</div>

<div class="result">
</div>

<div class="result">
</div>