Javascript - 深度相等比较

问题描述 投票:0回答:12

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

我想我已经有了总体思路;但是,正如我在评论中所述,程序不会检查对象中的第二个属性。我觉得我有结构/逻辑问题,只是以错误的方式使用递归,因为我最初打算循环遍历属性,使用递归来比较第一个属性的值,然后继续循环到下一个属性属性并再次比较。虽然,我不确定这是否可能?

我已经进行了大量的思考并尝试了几种不同的方法,但这是迄今为止我得到的最正确的答案。有什么可能的建议可以为我指明正确的方向吗?

javascript object recursion comparison equality
12个回答
52
投票

正如您所怀疑的,您正在返回所看到的第一个属性的匹配项。如果该属性不匹配,您应该返回

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


19
投票

感觉这个版本更具可读性(更容易理解)。不过,逻辑与最上面的答案非常相似。 (这次是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));
}

顺便说一下,deep equal 有一个 cheater 版本,它的作用就像一个魅力))但是,它的速度慢了大约 1.6 倍。

正如 Zero298 所注意到的,这种方法对属性排序很敏感,不应该被认真对待

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

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;

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

2
投票

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

注释:

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

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

1
投票

之前的所有答案都包含细微的错误,这些错误将导致它们在某些情况下失败。它们要么 1) 依赖于相同顺序的属性,要么 2) 在某些情况下返回不对称的结果,因此

deepEqual(a, b) !== deepEqual(b, a)
。这是一个改进的答案,假设如下:

  • 我们对“同值平等”感兴趣;我们希望 deepEqual(NaN, NaN) 返回
    true
    ,但
    deepEqual(0, -0)
    返回
    false
    我们只关心直接在对象上定义的可枚举的、字符串键控的属性(即由 
  • Object.keys()
  • 返回的那些属性)。 不需要对循环引用的完全支持。
  • /** * Tests whether two values are deeply equal using same-value equality. * * Two values are considered deeply equal iff 1) they are the same value, or * 2) they are both non-callable objects whose own, enumerable, string-keyed * properties are deeply equal. * * Caution: This function does not fully support circular references. Use this * function only if you are sure that at least one of the arguments has no * circular references. */ function deepEqual(x, y) { // If either x or y is not an object, then they are deeply equal iff they // are the same value. For our purposes, objects exclude functions, // primitive values, null, and undefined. if (typeof x !== "object" || x === null || typeof y !== "object" || y === null) { // We use Object.is() to check for same-value equality. To check for // strict equality, we would use x === y instead. return Object.is(x, y); } // Shortcut, in case x and y are the same object. Every object is // deeply equal to itself. if (x === y) return true; // Obtain the own, enumerable, string-keyed properties of x. We ignore // properties defined along x's prototype chain, non-enumerable properties, // and properties whose keys are symbols. const keys = Object.keys(x); // If x and y have a different number of properties, then they are not // deeply equal. if (Object.keys(y).length !== keys.length) return false; // For each own, enumerable, string property key of x: for (const key of keys) { // If key is not also an own enumerable property of y, or if x[key] and // y[key] are not themselves deeply equal, then x and y are not deeply // equal. Note that we don't just call y.propertyIsEnumerable(), // because y might not have such a method (for example, if it was // created using Object.create(null)), or it might not be the same // method that exists on Object.prototype. if (!Object.prototype.propertyIsEnumerable.call(y, key) || !deepEqual(x[key], y[key])) { return false; } } // x and y have the same properties, and all of those properties are deeply // equal, so x and y are deeply equal. return true; }


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>


0
投票


0
投票
deepEquel

的递归版本:

const deeplyEqual = function(obj1, obj2) {
    // if both objects are the same, they're equal
    if (obj1 === obj2) return true;
    // if either object is null, they're not equal
    if (obj1 === null || obj2 === null) return false;
    // if the string representation of both objects are not equal, they're not equal
    if (String(obj1) !== String(obj2)) return false;

    // if the objects are not arrays or objects, they're equal if they're equal
    if (typeof obj1 !== 'object') {
        return obj1 === obj2;
    }

    // if the objects are arrays, they're equal if they have the same length and each element is deeply equal
    if (Array.isArray(obj1)) {
        if (obj1.length !== obj2.length) return false;

        for (let i = 0; i < obj1.length; i++) {
            if (!areDeeplyEqual(obj1[i], obj2[i])) return false;
        }

        return true;
    }

    // if the objects are objects, they're equal if they have the same keys and values are deeply equal
    if (Object.keys(obj1).length !== Object.keys(obj2).length) return false;

    for (const key in obj1) {
        if (!areDeeplyEqual(obj1[key], obj2[key])) return false;
    }

    return true;
};



0
投票

const hasOwnProperty = Object.prototype.hasOwnProperty; export function deepEquals(x, y, objCheckLayersLeft = -1) { // fast route: if values are identical, return true if (Object.is(x, y)) return true; // values are non-identical; so if one is a primitive or null/undefined, they can't be equal, thus return false if (typeof x !== "object" || x == null || typeof y !== "object" || y == null) return false; // values are non-identical objects; so if we've reached the object-check layer-limit, return false if (objCheckLayersLeft == 0) return false; // check for differences in the objects' field-names and field-values; if any such difference is found, return false // NOTE: Objects.keys() excludes non-enumerable properties; to include them, use Object.getOwnPropertyNames() instead const xKeys = Object.keys(x), yKeys = Object.keys(y); if (xKeys.length != yKeys.length) return false; for (const key of xKeys) { if (!hasOwnProperty.call(y, key)) return false; if (!deepEquals(x[key], y[key], objCheckLayersLeft - 1)) return false; } // none of the checks found a difference, so the objects must be equal return true; } // because of the objCheckLayersLeft parameter, we can easily create a shallowEquals function export function shallowEquals(objA, objB) { return deepEquals(objA, objB, 1); }



-1
投票

我的缺陷(如果还有更多缺陷请告诉我)是对象属性也必须按精确顺序排列。我更喜欢@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>

© www.soinside.com 2019 - 2024. All rights reserved.