Javascript - deepEqual比较

时间:2014-08-22 21:49:33

标签: javascript recursion

问题(来自Eloquent Javascript第2版,第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;
}

我认为我有一般的想法;但是,就像我在评论中所说的那样,程序不会检查对象中的第二个属性。我觉得我有结构/逻辑问题而且只是以错误的方式使用递归,因为我原本打算循环遍历属性,使用递归来比较第一个属性的值,然后继续循环到下一个属性财产并再次比较。虽然,我不确定这是否可能?

我已经给出了很多想法并尝试了几种不同的方法,但这是我到目前为止最正确的答案。任何可能的提示都指出我正确的方向?

8 个答案:

答案 0 :(得分:22)

正如您所怀疑的那样,您将返回所看到的第一个属性的匹配。如果该属性不匹配,您应该返回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

答案 1 :(得分:5)

感觉这个版本更具可读性(更容易理解)。尽管如此,逻辑与最佳答案非常相似。 (这次是ES6)

function deepEqual(obj1, obj2) {

    if(obj1 === obj2) // it's 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);
}

答案 2 :(得分:2)

您可以使用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;

答案 3 :(得分:2)

我对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

答案 4 :(得分:1)

尽管它更为冗长,但也许此选项更易于阅读:

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) {
                  deepEqual(elem1[key], elem2[key]);
              }
              else {
                if(elem1[key] !== elem2[key]) {
                    return false;
                }
              }
          }
        }
      }
    else {
        return false;
    }
    return true;
  }

答案 5 :(得分:0)

<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>

答案 6 :(得分:-1)

我刚刚完成了这一章,并希望展示我的作品。

我的缺陷(让我知道是否有更多)是对象属性也必须按照确切的顺序。我更喜欢@paul和@ danni的解决方案。

&#13;
&#13;
// 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);
&#13;
body {
  font-family: monospace;
}

span {
  color: #a0a0a0;
}

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

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

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

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

<div class="result">
</div>
&#13;
&#13;
&#13;

答案 7 :(得分:-1)

基于Paul Roub接受的答案,我需要它也要与函数值匹配,并且我希望它更加简洁,所以我对其进行了重构。

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

注释:

  • 您仅传递了两个参数:x和y(z供内部使用)。
  • 如果变量之一是nullundefined,它将返回该值而不是false,但是结果仍然是“ falsey”,因此我可以接受。要解决此问题,您可以将所有出现的y &&更改为(y || !1) &&,将x &&更改为(x || !1) &&
  • 如果您绝对不希望在其中提供函数/回调 您的对象,然后删除|| typeof x == "function" && y && x.toString() == y.toString()