如何使用Javascript或lodash进行两个对象属性的浅比较?

39

在JavaScript或lodash中,有没有一种可以进行浅层比较的方法,不会进入并比较对象内部的对象内容?请注意,我确实检查了lodash,但它似乎执行深度比较,而我不想这样做。

var a = { x: 1, y: 2}
var b = { x: 1, y: 3}

例如,有没有一种方法可以比较ab


2
这里的情况不同,对吧? - thefourtheye
是的。我知道这个例子没有包括一个对象,但如果它包括对象,我希望不检查该对象的内容(两个对象都存在)。 - Samantha J T Star
我仍然不太清楚,但你可以尝试 JSON.stringify(a) === JSON.stringify(b) - thefourtheye
@thefourtheye,(1)这似乎并不肤浅,(2)在JSON.stringify中没有关于顺序的保证。 - Paul Draper
@PaulDraper 我同意,这可能不是肤浅的,但由于键相同,我认为顺序并不重要。 - thefourtheye
我很惊讶这个功能在 Lodash 中 [没有](https://github.com/lodash/lodash/issues/2340)。 - ELLIOTTCABLE
12个回答

76

ES6的简单方法:

const shallowCompare = (obj1, obj2) =>
  Object.keys(obj1).length === Object.keys(obj2).length &&
  Object.keys(obj1).every(key => obj1[key] === obj2[key]);

在此,我添加了对象键的数量相等检查,以便以下比较失败(通常不被考虑的重要情况):

shallowCompare({ x: 1, y: 3}, { x: 1, y: 3, a: 1}); // false

2019 更新。根据 Andrew Rasmussen 的评论,我们还需要考虑 undefined 情况。之前的方法存在问题,因为以下比较返回 true

({ foo: undefined })['foo'] === ({ bar: undefined })['foo'] // true

所以需要进行显式键存在性检查。可以使用hasOwnProperty来完成:

const shallowCompare = (obj1, obj2) =>
  Object.keys(obj1).length === Object.keys(obj2).length &&
  Object.keys(obj1).every(key => 
    obj2.hasOwnProperty(key) && obj1[key] === obj2[key]
  );

5
对于具有未定义值的键,此方法无效。shallowCompare({ foo: undefined, }, { bar: undefined, }); // 返回 true - Andrew Rasmussen
6
我已更新答案并涵盖了“未定义”的情况。感谢您的评论和故障代码示例! - dhilt
9
请改用 Object.prototype.hasOwnProperty.call(obj2, key) 以通过 ESLint 检查:https://eslint.org/docs/rules/no-prototype-builtins - theicfire

31
function areEqualShallow(a, b) {
    for(var key in a) {
        if(!(key in b) || a[key] !== b[key]) {
            return false;
        }
    }
    for(var key in b) {
        if(!(key in a) || a[key] !== b[key]) {
            return false;
        }
    }
    return true;
}

注意:

  • 由于这是浅层比较,areEqualShallow({a:{}}, {a:{}}) 返回 false。

  • areEqualShallow({a:undefined}, {}) 返回 false。

  • 包括原型链上的任何属性。

  • 这使用 === 进行比较。我假设这是您想要的。 NaN === NaN 是一个可能会产生意外结果的情况。如果 === 不是您想要的,请用您想要的比较方法替换。


编辑:如果每个对象中都有相同的键,则

function areEqualShallow(a, b) {
    for(var key in a) {
        if(a[key] !== b[key]) {
            return false;
        }
    }
    return true;
}

谢谢。我肯定的一件事是每个对象中都有所有的键,只有值可能不同。这可以简化为一个for循环吗? - Samantha J T Star
1
应该检查 typeof 吗?如果 a[key]b[key] 是子对象? - sabithpocker
1
+1,但需要注意===有一些边缘情况。特别是,NaN不等于自身,而+0和-0彼此相等(尽管在某些情况下行为不同)。 - ruakh
@sabithpocker,你为什么想那样做? - Paul Draper
2
@sabithpocker,你的例子突显了“浅”比较和“深”比较之间的区别。浅比较认为它们不相等;而深比较认为它们相等。 - Paul Draper
显示剩余6条评论

6
记住,此方法只适用于浅层次的字符串和数字。
function equals(obj1, obj2) {
  return Object.keys(obj1)
    .concat(Object.keys(obj2))
    .every(key => {
      return obj1[key] === obj2[key];
    });
}

它也可以很好地检查对象引用,这正是您对浅比较应该期望的。 - marksyzm
1
这对于值为undefined的键无效。equals({ foo: undefined, }, { bar: undefined, }); // 返回true - Andrew Rasmussen
@AndrewRasmussen 技术上说,这两个是浅相等的,因为{ foo: undefined }{ bar: undefined }与描述{}一样,只是多了一些步骤。 - Steven
我不认为 {} 和 { foo: undefined } 是浅相等的,因为它们具有不同的键。 - Andrew Rasmussen

6
保罗·德拉珀的解决方案可以通过在第二次遍历中删除比较来进行优化。

function areEqualShallow(a, b) {
  for (let key in a) {
    if (!(key in b) || a[key] !== b[key]) {
      return false;
    }
  }
  for (let key in b) {
    if (!(key in a)) {
      return false;
    }
  }
  return true;
}


1
感谢关注细节! - corolla

5
此处内容摘自 fbjs
/**
 * Copyright (c) 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @typechecks
 *
 */

/*eslint-disable no-self-compare */

'use strict';

var hasOwnProperty = Object.prototype.hasOwnProperty;

/**
 * inlined Object.is polyfill to avoid requiring consumers ship their own
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
 */
function is(x, y) {
    // SameValue algorithm
    if (x === y) {
        // Steps 1-5, 7-10
        // Steps 6.b-6.e: +0 != -0
        return x !== 0 || 1 / x === 1 / y;
    } else {
        // Step 6.a: NaN == NaN
        return x !== x && y !== y;
    }
}

/**
 * Performs equality by iterating through keys on an object and returning false
 * when any key has values which are not strictly equal between the arguments.
 * Returns true when the values of all keys are strictly equal.
 */
function shallowEqual(objA, objB) {
    if (is(objA, objB)) {
        return true;
    }

    if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
        return false;
    }

    var keysA = Object.keys(objA);
    var keysB = Object.keys(objB);

    if (keysA.length !== keysB.length) {
        return false;
    }

    // Test for A's keys different from B.
    for (var i = 0; i < keysA.length; i++) {
        if (!hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
            return false;
        }
    }

    return true;
}

module.exports = shallowEqual;

如果您需要使用它,我建议将其复制到您自己的项目中。因为他们的README清楚地说明,他们可能会在没有警告的情况下删除或修改lib中的此代码和其他任何代码。


1
这里有一个直接链接到代码的链接 - zhirzh

1
要进行“浅层”比较,应忽略继承属性,并且NaN应等于NaN,以下代码可实现此功能。它检查每个对象是否具有相同的自有属性,并且值为===或两者都为NaN
function checkProperties(a, b) {
    var equal = true;

    // For each property of a
    for (var p in a) {

        // Check that it's an own property
        if (a.hasOwnProperty(p)) {

            // Check that b has a same named own property and that the values
            // are === or both are NaN
            if (!b.hasOwnProperty(p) || 
               (b[p] !== a[p] && !(typeof b[p] == 'number' && typeof a[p] == 'number' && isNaN(b[p] && isNaN(a[p]))))) {

                // If not, set equal to false
                equal = false;
            }
        }

        // If equal is false, stop processing properties
        if (!equal) break;
    }
    return equal;
}

使用最近的功能,如Object.keys来获取自身属性,然后

function checkProperties(a, b) {
  return Object.keys(a).every(function(p) {
    return b.hasOwnProperty(p) && 
           (b[p] == a[p] || (typeof a[p] == 'number' && typeof b[p] == 'number' && isNaN(b[p]) && isNaN(a[p])));
   });
}

// Compare a to b and b to a
function areEqualShallow(a, b) {
  return checkProperties(a, b) && checkProperties(b, a);
}

// Minimal testing
var a = {foo:'a', bar:2};
var b = {foo:'a', bar:2};
var c = {foo:'c', bar:2};
var d = {foo:'a', bar:2, fum:0};

console.log('a equal to b? ' + areEqualShallow(a,b)); // true
console.log('a equal to c? ' + areEqualShallow(a,c)); // false
console.log('a equal to d? ' + areEqualShallow(a,d)); // false

随着新功能的加入,checkProperties函数可以在一定程度上得到简化:


我认为立即返回false比手动设置标志并从循环中跳出更简洁。 - George
是的,你说得对。此外,在 isNaN 之前应该有一个 typeof 测试,因为它只能用于数字类型,isNaN(someNonNumber) 总是返回 true,所以不匹配的值会通过。;-) - RobG
此外,你应该始终使用 === 而不是 ==。双等号容易出错。 - George

1
const shallowEq = (a, b) =>
  [...Object.keys(a), ...Object.keys(b)].every((k) => b[k] === a[k]);

如果您确实需要检查未定义的值,则此扩展应该满足@AndrewRasmussen的要求:
const shallowEq2 = (a, b) =>
  [...Object.keys(a), ...Object.keys(b)].every(k => b[k] === a[k] && a.hasOwnProperty(k) && b.hasOwnProperty(k)); 

在大多数情况下,您实际上并不需要进行所有检查,您只想看到 b 是否包含 a 的所有内容。然后,以 a 为中心的检查将非常简洁:

const shallowEq3 = (a, b) => Object.keys(a).every(k => b[k] === a[k]);

不要错过这句话,即“当您只想看到b是否包含a中的所有内容时,使用shallowEq3”。它将返回true,如果 a = { x: 1 }, b = { x: 1, y: 2 } - apostl3pol

0
var a = { x: 1, y: 2}
var b = { x: 1, y: 3}

function shalComp (obj1, obj2) {
 var verdict = true;
 for (var key in obj1) {
  if (obj2[key] != obj1[key]) {
   verdict = false;
  }
 }
 return verdict;
}

0
我在Github上发现了一个专门为此目的设计的库:https://github.com/dashed/shallowequal。它已经获得了超过170个星星,并且有测试代码。
我正在将其合并到我的工具链中进行全面测试。

0

const isEqual = (a, b) => {
  // compare keys
  const xKeys = Object.keys(a);
  const bKeys = Object.keys(b);

  if (xKeys.length !== bKeys.length) {
    return false;
  }

  // compare values
  for (let objKeys in xKeys) {
    if (xKeys[objKeys !== bKeys[objKeys]]) {
      return false;
    }
  }
  return true;
};

var a = {
  x: 1,
  y: 2,
};

var b = {
  x: 1,
  y: 2,
};

console.log(isEqual(a, b)); // true

你可以看到这个视频对你的问题非常有帮助:JS教程:查找两个对象值是否相等


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接