如何确定两个 JavaScript 对象的相等性?

How can I determine equality for two JavaScript objects?

提问人: 提问时间:10/14/2008 最后编辑:Peter Mortensen 更新时间:10/4/2023 访问量:877005

问:

严格相等运算符将告诉您两个对象类型是否相等。但是,有没有办法判断两个对象是否相等,就像 Java 中的哈希代码值一样

Stack Overflow 问题 JavaScript 中是否有任何类型的 hashCode 函数? 与这个问题类似,但需要更学术的答案。上面的场景说明了为什么有必要有一个,我想知道是否有任何等效的解决方案

JavaScript 对象 等于 哈希码

评论

5赞 Praveen 5/6/2014
还要研究这个问题 stackoverflow.com/q/1068834/1671639
55赞 Heinzi 9/24/2014
请注意,即使在 Java 中,也不意味着它等于 .这是一个必要条件,而不是一个充分条件。a.hashCode() == b.hashCode()ab
7赞 th317erd 2/25/2017
如果你必须比较代码中的对象,那么你可能写错了代码。更好的问题可能是:“我怎样才能写出这段代码,这样我就不必比较对象了?
12赞 El Mac 5/14/2018
你能@th317erd解释一下自己吗?...
3赞 VLAZ 5/15/2018
@ElMac 我不能直接代表这个人说话,但我确实同意这种说法,我的思维过程是,很多时候 JS 对象相当大。你很少拥有像 .如果你这样做了,你必须通过完全相等来搜索它们,这似乎是一种浪费。大多数情况下,您的普通对象仍然会有很多属性 - 其中一个是 uinique,或者您希望一个是唯一的,例如,某种 ID。然后,您可以按它进行搜索,而无需检查是否每件事都匹配。person = { name: "fred", age: 42 }

答:

27赞 nickf 10/14/2008 #1

您是否正在尝试测试两个对象是否相等?即:它们的性质是相等的吗?

如果是这种情况,您可能已经注意到这种情况:

var a = { foo : "bar" };
var b = { foo : "bar" };
alert (a == b ? "Equal" : "Not equal");
// "Not equal"

您可能需要执行如下操作:

function objectEquals(obj1, obj2) {
    for (var i in obj1) {
        if (obj1.hasOwnProperty(i)) {
            if (!obj2.hasOwnProperty(i)) return false;
            if (obj1[i] != obj2[i]) return false;
        }
    }
    for (var i in obj2) {
        if (obj2.hasOwnProperty(i)) {
            if (!obj1.hasOwnProperty(i)) return false;
            if (obj1[i] != obj2[i]) return false;
        }
    }
    return true;
}

显然,该函数可以进行相当多的优化,并能够进行深度检查(处理嵌套对象:),但你明白了。var a = { foo : { fu : "bar" } }

正如 FOR 所指出的,您可能必须根据自己的目的进行调整,例如:不同的类可能对“平等”有不同的定义。如果您只是使用普通对象,上述内容可能就足够了,否则自定义函数可能是要走的路。MyClass.equals()

评论

0赞 briancollins081 3/6/2020
这是一个很长的方法,但它完全测试对象,而不对每个对象中的属性顺序做出任何假设。
0赞 daydr3amer 3/22/2022
如果属性是其他对象的数组,则不起作用
70赞 Joel Anair 10/14/2008 #2

如果您使用的是 JSON 库,则可以将每个对象编码为 JSON,然后比较生成的字符串是否相等。

var obj1={test:"value"};
var obj2={test:"value2"};

alert(JSON.encode(obj1)===JSON.encode(obj2));

注意:虽然这个答案在许多情况下都有效,但正如一些人在评论中指出的那样,由于各种原因,它是有问题的。在几乎所有情况下,您都希望找到一个更强大的解决方案。

评论

123赞 Guido 10/14/2008
有趣,但在我看来有点棘手。例如,你能 100% 保证对象属性总是以相同的顺序生成吗?
33赞 Joel Anair 10/15/2008
这是一个很好的问题,并提出了另一个问题,即两个在不同顺序上具有相同属性的对象是否真的相等。我猜,这取决于你说的平等是什么意思。
11赞 Stephen Belanger 4/9/2011
请注意,大多数编码器和字符串化器会忽略函数并将非有限数(如 NaN)转换为 null。
4赞 Juzer Ali 3/26/2012
我同意 Guido 的观点,属性的顺序很重要,不能保证。@JoelAnair,我认为如果属性的值相等,则两个具有不同顺序相同属性的对象应被视为相等。
6赞 Roy Tinker 10/29/2014
可以与备用 JSON 字符串化器一起使用,该字符串化器可以一致地对对象键进行排序。
224赞 AnthonyWJones 10/14/2008 #3

简短的回答

简单的答案是:不,没有通用的方法可以确定一个对象与另一个对象相等。例外情况是,严格来说,对象是无类型的。

长答案

这个概念是一个 Equals 方法,该方法比较对象的两个不同实例,以指示它们在值级别上是否相等。但是,由特定类型来定义应如何实现方法。对具有基元值的属性进行迭代比较可能还不够:对象可能包含与相等性无关的属性。例如Equals

 function MyClass(a, b)
 {
     var c;
     this.getCLazy = function() {
         if (c === undefined) c = a * b // imagine * is really expensive
         return c;
     }
  }

在上面的这个例子中,确定MyClass的任何两个实例是否相等并不重要,只有和重要。在某些情况下,实例之间可能会有所不同,但在比较过程中并不显着。cabc

请注意,当成员本身也可能是某种类型的实例时,此问题适用,并且每个实例都需要具有确定相等性的方法。

更复杂的是,在JavaScript中,数据和方法之间的区别是模糊的。

对象可以引用要作为事件处理程序调用的方法,这可能不会被视为其“值状态”的一部分。而另一个对象很可能被分配一个执行重要计算的函数,从而使这个实例与其他实例不同,仅仅是因为它引用了一个不同的函数。

如果一个对象的现有原型方法被另一个函数覆盖,该怎么办?它是否仍可被视为等同于其他方面相同的另一个实例?这个问题只能在每种类型的每种特定情况下回答。

如前所述,例外是严格无类型的对象。在这种情况下,唯一明智的选择是对每个成员进行迭代和递归比较。即便如此,人们也不得不问一个函数的“价值”是什么?

评论

209赞 chovy 8/22/2013
如果你使用下划线,你可以这样做_.isEqual(obj1, obj2);
15赞 lcn 9/26/2013
@Harsh,答案没有给出任何解决方案,因为没有。即使在 Java 中,也没有灵丹妙药来比较对象相等性,正确实现该方法并非易事,这就是为什么在 Effective Java 中有这样一个主题。.equals
4赞 sethro 1/18/2014
@Kumar Harsh,使两个对象相等的原因非常特定于应用程序;并非必须考虑对象的每个属性,因此暴力破解对象的每个属性也不是具体的解决方案。
1赞 Aaron Franke 12/18/2020
什么是下划线?是图书馆吗?用于检查对象是否相等的最小代码片段大小是多少?
1赞 Gifford N. 5/29/2022
@AaronFranke是的,下划线是一个类似于 Lodash 的实用程序库。我认为对于这个用例,lodash 更好,因为它允许比较深度嵌套的对象,并且已知性能更高。请参阅此处的比较:geeksforgeeks.org/difference-between-lodash-and-underscore
198赞 Daniel X Moore 5/20/2009 #4

JavaScript 中 for Objects 的默认相等运算符在引用内存中的相同位置时生成 true。

var x = {};
var y = {};
var z = x;

x === y; // => false
x === z; // => true

如果你需要一个不同的相等运算符,你需要在你的类中添加一个方法或类似的东西,你的问题域的具体情况将决定这到底意味着什么。equals(other)

下面是一个扑克牌示例:

function Card(rank, suit) {
  this.rank = rank;
  this.suit = suit;
  this.equals = function(other) {
     return other.rank == this.rank && other.suit == this.suit;
  };
}

var queenOfClubs = new Card(12, "C");
var kingOfSpades = new Card(13, "S");

queenOfClubs.equals(kingOfSpades); // => false
kingOfSpades.equals(new Card(13, "S")); // => true

评论

0赞 scotts 11/15/2012
如果对象可以转换为 JSON 字符串,则它使 equals() 函数变得简单。
6赞 Daniel X Moore 11/15/2012
@scotts 并非总是如此。对于紧密循环中的复杂对象,将对象转换为 JSON 并比较字符串可能会占用大量计算资源。对于简单的对象来说,这可能并不重要,但实际上它确实取决于您的具体情况。正确的解决方案可能就像比较对象 ID 或检查每个属性一样简单,但其正确性完全由问题域决定。
1赞 devsathish 10/28/2013
我们难道不应该也比较一下数据类型吗?!return other.rank === this.rank & other.suit === this.suit;
1赞 Daniel X Moore 10/29/2013
@devsathish可能不是。在 JavaScript 中,类型非常快速且松散,但如果在您的域中类型很重要,那么您可能还需要检查类型。
18赞 Stijn de Witt 3/2/2016
@scotts 转换为 JSON 的另一个问题是字符串中属性的顺序变得很重要。 !=={x:1, y:2}{y:2, x:1}
3赞 Liam 5/27/2010 #5

需要一个比发布更通用的对象比较功能,我制作了以下内容。评论赞赏...

Object.prototype.equals = function(iObj) {
  if (this.constructor !== iObj.constructor)
    return false;
  var aMemberCount = 0;
  for (var a in this) {
    if (!this.hasOwnProperty(a))
      continue;
    if (typeof this[a] === 'object' && typeof iObj[a] === 'object' ? !this[a].equals(iObj[a]) : this[a] !== iObj[a])
      return false;
    ++aMemberCount;
  }
  for (var a in iObj)
    if (iObj.hasOwnProperty(a))
      --aMemberCount;
  return aMemberCount ? false : true;
}

评论

2赞 Roy Tinker 10/29/2014
修改时要非常小心 -- 在绝大多数情况下,不建议这样做(添加出现在所有 for..例如,in 循环)。也许考虑一下?Object.prototypeObject.equals = function(aObj, bObj) {...}
702赞 coolaj86 7/8/2010 #6

为什么要重新发明轮子?试试 Lodash。它有许多必备的函数,例如 isEqual()。

_.isEqual(object, other);

它将使用 ECMAScript 5 和本机优化(如果浏览器中可用)暴力检查每个键值 - 就像本页的其他示例一样。

注意:以前这个答案推荐了下划线.js,但 lodash 在修复错误和解决一致性问题方面做得更好。

评论

60赞 mckoss 8/28/2010
Underscore 的 isEqual 函数非常好(但您必须拉入他们的库才能使用它 - 大约 3K gzipped)。
14赞 Dale Anderson 9/7/2012
即使您负担不起将下划线作为依赖项,也可以拉出 isEqual 函数,满足许可证要求并继续前进。这是迄今为止 stackoverflow 上提到的最全面的相等性测试。
8赞 coolaj86 2/13/2013
Underscore 有一个分支叫做 LoDash,作者非常关注诸如此类的一致性问题。使用 LoDash 进行测试,看看您能得到什么。
13赞 Rob Fox 6/1/2015
@mckoss,如果您不希望整个库 npmjs.com/package/lodash.isequal,则可以使用独立模块
14赞 zfrisch 1/24/2018
说“哦,就用X包吧!”有什么用?你告诉这个人抽象和批量他们的代码,而没有真正解释如何自己真正获得解决方案。我并不是说不要使用包或建议它们,但 JavaScript 生态系统是变化无常的,你应该促进对解决方案的实际理解,而不仅仅是暂时绕过它的方法。
24赞 Ates Goral 6/15/2012 #7

如果您手头有深度复制功能,则可以使用以下技巧在匹配属性顺序的同时可使用:JSON.stringify

function equals(obj1, obj2) {
    function _equals(obj1, obj2) {
        return JSON.stringify(obj1)
            === JSON.stringify($.extend(true, {}, obj1, obj2));
    }
    return _equals(obj1, obj2) && _equals(obj2, obj1);
}

演示:http://jsfiddle.net/CU3vb/3/

理由:

由于 的属性将逐个复制到克隆中,因此它们将在克隆中的顺序被保留。当 的属性被复制到克隆时,由于已经存在的属性将被简单地覆盖,它们在克隆中的顺序将被保留。obj1obj2obj1

评论

11赞 Jo Liss 3/8/2013
我不认为跨浏览器/引擎可以保证订单保存。
0赞 Ates Goral 3/8/2013
@JoLiss 需要;)引文我记得在多个浏览器中对此进行了测试,获得了一致的结果。但是,当然,没有人能保证在未来的浏览器/引擎中保持相同的行为。这充其量只是一个技巧(正如答案中已经指出的那样),我并不是说它是比较对象的可靠方法。
1赞 Jo Liss 3/8/2013
当然,这里有一些提示:ECMAScript 规范说对象是“无序的”;这是对当前浏览器上实际发散行为的回答
3赞 Ates Goral 3/8/2013
@JoLiss 谢谢你!但请注意,我从未要求保留代码和编译对象之间的顺序。我要求保留其值被替换的属性的顺序。这就是我的解决方案的关键:使用 mixin 来覆盖属性值。假设实现通常选择使用某种哈希图,则仅替换值应保留键的顺序。事实上,我在不同的浏览器中测试过的正是这一点。
1赞 willeM_ Van Onsem 5/19/2015
@AtesGoral:是否有可能使这个约束更明确一些(粗体,...)。大多数人只是在不阅读周围的文本的情况下进行复制粘贴......
87赞 Ebrahim Byagowi 5/28/2013 #8

这是我的版本。它使用了 ES5 中引入的新 Object.keys 功能以及来自 +、+ 和 + 的想法/测试:

function objectEquals(x, y) {
    'use strict';

    if (x === null || x === undefined || y === null || y === undefined) { return x === y; }
    // after this just checking type of one would be enough
    if (x.constructor !== y.constructor) { return false; }
    // if they are functions, they should exactly refer to same one (because of closures)
    if (x instanceof Function) { return x === y; }
    // if they are regexps, they should exactly refer to same one (it is hard to better equality check on current ES)
    if (x instanceof RegExp) { return x === y; }
    if (x === y || x.valueOf() === y.valueOf()) { return true; }
    if (Array.isArray(x) && x.length !== y.length) { return false; }

    // if they are dates, they must had equal valueOf
    if (x instanceof Date) { return false; }

    // if they are strictly equal, they both need to be object at least
    if (!(x instanceof Object)) { return false; }
    if (!(y instanceof Object)) { return false; }

    // recursive object equality check
    var p = Object.keys(x);
    return Object.keys(y).every(function (i) { return p.indexOf(i) !== -1; }) &&
        p.every(function (i) { return objectEquals(x[i], y[i]); });
}


///////////////////////////////////////////////////////////////
/// The borrowed tests, run them by clicking "Run code snippet"
///////////////////////////////////////////////////////////////
var printResult = function (x) {
    if (x) { document.write('<div style="color: green;">Passed</div>'); }
    else { document.write('<div style="color: red;">Failed</div>'); }
};
var assert = { isTrue: function (x) { printResult(x); }, isFalse: function (x) { printResult(!x); } }
assert.isTrue(objectEquals(null,null));
assert.isFalse(objectEquals(null,undefined));
assert.isFalse(objectEquals(/abc/, /abc/));
assert.isFalse(objectEquals(/abc/, /123/));
var r = /abc/;
assert.isTrue(objectEquals(r, r));

assert.isTrue(objectEquals("hi","hi"));
assert.isTrue(objectEquals(5,5));
assert.isFalse(objectEquals(5,10));

assert.isTrue(objectEquals([],[]));
assert.isTrue(objectEquals([1,2],[1,2]));
assert.isFalse(objectEquals([1,2],[2,1]));
assert.isFalse(objectEquals([1,2],[1,2,3]));

assert.isTrue(objectEquals({},{}));
assert.isTrue(objectEquals({a:1,b:2},{a:1,b:2}));
assert.isTrue(objectEquals({a:1,b:2},{b:2,a:1}));
assert.isFalse(objectEquals({a:1,b:2},{a:1,b:3}));

assert.isTrue(objectEquals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}},{1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}));
assert.isFalse(objectEquals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}},{1:{name:"mhc",age:28}, 2:{name:"arb",age:27}}));

Object.prototype.equals = function (obj) { return objectEquals(this, obj); };
var assertFalse = assert.isFalse,
    assertTrue = assert.isTrue;

assertFalse({}.equals(null));
assertFalse({}.equals(undefined));

assertTrue("hi".equals("hi"));
assertTrue(new Number(5).equals(5));
assertFalse(new Number(5).equals(10));
assertFalse(new Number(1).equals("1"));

assertTrue([].equals([]));
assertTrue([1,2].equals([1,2]));
assertFalse([1,2].equals([2,1]));
assertFalse([1,2].equals([1,2,3]));
assertTrue(new Date("2011-03-31").equals(new Date("2011-03-31")));
assertFalse(new Date("2011-03-31").equals(new Date("1970-01-01")));

assertTrue({}.equals({}));
assertTrue({a:1,b:2}.equals({a:1,b:2}));
assertTrue({a:1,b:2}.equals({b:2,a:1}));
assertFalse({a:1,b:2}.equals({a:1,b:3}));

assertTrue({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}.equals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}));
assertFalse({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}.equals({1:{name:"mhc",age:28}, 2:{name:"arb",age:27}}));

var a = {a: 'text', b:[0,1]};
var b = {a: 'text', b:[0,1]};
var c = {a: 'text', b: 0};
var d = {a: 'text', b: false};
var e = {a: 'text', b:[1,0]};
var i = {
    a: 'text',
    c: {
        b: [1, 0]
    }
};
var j = {
    a: 'text',
    c: {
        b: [1, 0]
    }
};
var k = {a: 'text', b: null};
var l = {a: 'text', b: undefined};

assertTrue(a.equals(b));
assertFalse(a.equals(c));
assertFalse(c.equals(d));
assertFalse(a.equals(e));
assertTrue(i.equals(j));
assertFalse(d.equals(k));
assertFalse(k.equals(l));

// from comments on stackoverflow post
assert.isFalse(objectEquals([1, 2, undefined], [1, 2]));
assert.isFalse(objectEquals([1, 2, 3], { 0: 1, 1: 2, 2: 3 }));
assert.isFalse(objectEquals(new Date(1234), 1234));

// no two different function is equal really, they capture their context variables
// so even if they have same toString(), they won't have same functionality
var func = function (x) { return true; };
var func2 = function (x) { return true; };
assert.isTrue(objectEquals(func, func));
assert.isFalse(objectEquals(func, func2));
assert.isTrue(objectEquals({ a: { b: func } }, { a: { b: func } }));
assert.isFalse(objectEquals({ a: { b: func } }, { a: { b: func2 } }));

评论

0赞 Roy Tinker 10/29/2014
objectEquals([1,2,undefined],[1,2])返回true
0赞 Roy Tinker 10/29/2014
objectEquals([1,2,3],{0:1,1:2,2:3})也返回 -- 例如,没有类型检查,只有键/值检查。true
0赞 Roy Tinker 10/29/2014
objectEquals(new Date(1234),1234)返回true
1赞 Triynko 11/8/2016
if (x.constructor !== y.constructor) { return false;当比较不同窗口中的两个“new String('a')”时,这将中断。对于值相等,您必须检查两个对象上的 String.isString,然后使用松散的相等检查“a == b”。
1赞 Triynko 11/8/2016
“价值”平等和“严格”平等之间存在巨大差异,它们不应该以同样的方式实现。值相等不应该关心类型,除了基本结构,这是以下 4 个之一:“对象”(即键/值对的集合)、“数字”、“字符串”或“数组”。就是这样。任何不是数字、字符串或数组的内容都应作为一组键/值对进行比较,而不管构造函数是什么(跨窗口安全)。比较对象时,将文字数字的值和 Number 的实例相等,但不要将字符串强制为数字。
86赞 Troy Harvey 9/28/2013 #9

如果您在 AngularJS 中工作,该函数将确定两个对象是否相等。在 Ember.js 中使用 .angular.equalsisEqual

  • angular.equals- 有关此方法的更多信息,请参阅文档源代码。它也对数组进行了深入的比较。
  • Ember.js - 有关此方法的更多信息,请参阅文档源代码。它不会对数组进行深入比较。isEqual

var purple = [{"purple": "drank"}];
var drank = [{"purple": "drank"}];

if(angular.equals(purple, drank)) {
    document.write('got dat');
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>

3赞 Mirek Rusin 3/12/2014 #10

如果要比较 JSON 对象,可以使用 https://github.com/mirek/node-rus-diff

npm install rus-diff

用法:

a = {foo:{bar:1}}
b = {foo:{bar:1}}
c = {foo:{bar:2}}

var rusDiff = require('rus-diff').rusDiff

console.log(rusDiff(a, b)) // -> false, meaning a and b are equal
console.log(rusDiff(a, c)) // -> { '$set': { 'foo.bar': 2 } }

如果两个对象不同,则返回一个与MongoDB兼容的类似对象。{$rename:{...}, $unset:{...}, $set:{...}}

30赞 Rafael Xavier 9/24/2014 #11

在 Node.js 中,您可以使用其原生 .更多的信息: http://nodejs.org/api/assert.htmlrequire("assert").deepStrictEqual

例如:

var assert = require("assert");
assert.deepStrictEqual({a:1, b:2}, {a:1, b:3}); // will throw AssertionError

另一个返回 / 而不是返回错误的示例:truefalse

var assert = require("assert");

function deepEqual(a, b) {
    try {
      assert.deepEqual(a, b);
    } catch (error) {
      if (error.name === "AssertionError") {
        return false;
      }
      throw error;
    }
    return true;
};

评论

0赞 Folusho Oladipo 6/2/2017
Chai也有这个功能。在这种情况下,您将使用:var foo = { a: 1 }; var bar = { a: 1 }; expect(foo).to.deep.equal(bar); // true;
0赞 Knute Knudsen 3/11/2018
某些版本的 Node.js 设置为 。在这种情况下,我会将 if 语句替换为 .error.name"AssertionError [ERR_ASSERTION]"if (error.code === 'ERR_ASSERTION') {
0赞 NetOperator Wibby 2/22/2020
我不知道该走的路是什么。我一直在绞尽脑汁,试图弄清楚为什么不起作用。匪夷所思。deepStrictEqualstrictEqual
4赞 th317erd 2/11/2015 #12

编辑:这种方法存在很大缺陷,并且充斥着自己的问题。我不推荐它,并希望投一些反对票!这是有问题的,因为 1) 有些东西不能比较(即函数),因为它们不能序列化,2) 它不是一种非常快速的比较方法,3) 它有排序问题,4) 如果实施不当,它可能会有冲突问题/误报,5) 它不能检查“精确性”(),而是基于值相等, 这通常不是比较方法所期望的。===

许多人没有意识到的这个问题的一个简单解决方案是对 JSON 字符串(每个字符)进行排序。这通常也比此处提到的其他解决方案更快:

function areEqual(obj1, obj2) {
    var a = JSON.stringify(obj1), b = JSON.stringify(obj2);
    if (!a) a = '';
    if (!b) b = '';
    return (a.split('').sort().join('') == b.split('').sort().join(''));
}

此方法的另一个有用之处在于,您可以通过将“replacer”函数传递给 JSON.stringify 函数 (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Example_of_using_replacer_parameter) 来筛选比较。下面将仅比较名为“derp”的所有对象键:

function areEqual(obj1, obj2, filter) {
    var a = JSON.stringify(obj1, filter), b = JSON.stringify(obj2, filter);
    if (!a) a = '';
    if (!b) b = '';
    return (a.split('').sort().join('') == b.split('').sort().join(''));
}
var equal = areEqual(obj1, obj2, function(key, value) {
    return (key === 'derp') ? value : undefined;
});

评论

1赞 th317erd 2/11/2015
哦,我也忘记了,但是如果它们是同一个对象,可以通过首先测试对象相等和早期保释来加快函数的速度:if (obj1 === obj2) return true;
14赞 okm 7/13/2015
areEqual({a: 'b'}, {b: 'a'})然后得到?true
0赞 th317erd 7/15/2015
是的,我在发布后意识到这个“解决方案”有问题。它需要在排序算法中做更多的工作才能真正正常工作。
0赞 ggorlen 3/29/2022
@th317erd如果您想改进您的帖子,可以编辑它。
13赞 Alan R. Soares 8/25/2015 #13

以下是 ES6/ES2015 中使用函数式方法的解决方案:

const typeOf = x => 
  ({}).toString
      .call(x)
      .match(/\[object (\w+)\]/)[1]

function areSimilar(a, b) {
  const everyKey = f => Object.keys(a).every(f)

  switch(typeOf(a)) {
    case 'Array':
      return a.length === b.length &&
        everyKey(k => areSimilar(a.sort()[k], b.sort()[k]));
    case 'Object':
      return Object.keys(a).length === Object.keys(b).length &&
        everyKey(k => areSimilar(a[k], b[k]));
    default:
      return a === b;
  }
}

演示可在此处获得

评论

0赞 Isaac Pak 1/10/2020
如果对象键的顺序已更改,则不起作用。
110赞 atmin 10/3/2015 #14

简短的功能实现:deepEqual

function deepEqual(x, y) {
  return (x && y && typeof x === 'object' && typeof y === 'object') ?
    (Object.keys(x).length === Object.keys(y).length) &&
      Object.keys(x).reduce(function(isEqual, key) {
        return isEqual && deepEqual(x[key], y[key]);
      }, true) : (x === y);
}

编辑:版本 2,使用 jib 的建议和 ES6 箭头函数:

function deepEqual(x, y) {
  const ok = Object.keys, tx = typeof x, ty = typeof y;
  return x && y && tx === 'object' && tx === ty ? (
    ok(x).length === ok(y).length &&
      ok(x).every(key => deepEqual(x[key], y[key]))
  ) : (x === y);
}

评论

13赞 jib 3/6/2016
您可以替换为 以简化。reduceevery
2赞 jib 4/21/2016
@nonkertompf确信他可以: .Object.keys(x).every(key => deepEqual(x[key], y[key]))
3赞 Greg 4/3/2018
当您比较两个日期时,此操作失败
5赞 AlexMorley-Finch 7/4/2018
deepEqual({}, []) 返回 true
5赞 atmin 7/16/2018
是的,如果您关心这种极端情况,丑陋的解决方案是替换为: (x === y): (x === y && (x != null && y != null || x.constructor === y.constructor))
16赞 jib 3/6/2016 #15

我使用这个函数来生成我的对象的副本,这些副本是JSON可比的:comparable

var comparable = o => (typeof o != 'object' || !o)? o :
  Object.keys(o).sort().reduce((c, key) => (c[key] = comparable(o[key]), c), {});

// Demo:

var a = { a: 1, c: 4, b: [2, 3], d: { e: '5', f: null } };
var b = { b: [2, 3], c: 4, d: { f: null, e: '5' }, a: 1 };

console.log(JSON.stringify(comparable(a)));
console.log(JSON.stringify(comparable(b)));
console.log(JSON.stringify(comparable(a)) == JSON.stringify(comparable(b)));
<div id="div"></div>

在测试中派上用场(大多数测试框架都有功能)。例如is

is(JSON.stringify(comparable(x)), JSON.stringify(comparable(y)), 'x must match y');

如果发现差异,则会记录字符串,从而使差异变得可观察:

x must match y
got      {"a":1,"b":{"0":2,"1":3},"c":7,"d":{"e":"5","f":null}},
expected {"a":1,"b":{"0":2,"1":3},"c":4,"d":{"e":"5","f":null}}.

评论

1赞 mech 7/25/2016
好主意(在我的情况下,要比较的对象只是键/值对,没有特殊的东西)
3赞 Sir_baaron 9/14/2016 #16

我遇到了同样的问题,并决定编写自己的解决方案。但是因为我还想将数组与对象进行比较,反之亦然,所以我设计了一个通用解决方案。我决定将函数添加到原型中,但可以很容易地将它们重写为独立函数。代码如下:

Array.prototype.equals = Object.prototype.equals = function(b) {
    var ar = JSON.parse(JSON.stringify(b));
    var err = false;
    for(var key in this) {
        if(this.hasOwnProperty(key)) {
            var found = ar.find(this[key]);
            if(found > -1) {
                if(Object.prototype.toString.call(ar) === "[object Object]") {
                    delete ar[Object.keys(ar)[found]];
                }
                else {
                    ar.splice(found, 1);
                }
            }
            else {
                err = true;
                break;
            }
        }
    };
    if(Object.keys(ar).length > 0 || err) {
        return false;
    }
    return true;
}

Array.prototype.find = Object.prototype.find = function(v) {
    var f = -1;
    for(var i in this) {
        if(this.hasOwnProperty(i)) {
            if(Object.prototype.toString.call(this[i]) === "[object Array]" || Object.prototype.toString.call(this[i]) === "[object Object]") {
                if(this[i].equals(v)) {
                    f = (typeof(i) == "number") ? i : Object.keys(this).indexOf(i);
                }
            }
            else if(this[i] === v) {
                f = (typeof(i) == "number") ? i : Object.keys(this).indexOf(i);
            }
        }
    }
    return f;
}

该算法分为两部分;equals 函数本身和一个函数,用于查找数组/对象中属性的数值索引。只需要 find 函数,因为 indexof 只查找数字和字符串,而不查找对象。

可以这样称呼它:

({a: 1, b: "h"}).equals({a: 1, b: "h"});

该函数返回 true 或 false,在本例中为 true。 该算法允许在非常复杂的对象之间进行比较:

({a: 1, b: "hello", c: ["w", "o", "r", "l", "d", {answer1: "should be", answer2: true}]}).equals({b: "hello", a: 1, c: ["w", "d", "o", "r", {answer1: "should be", answer2: true}, "l"]})

上面的示例将返回 true,即使属性具有不同的顺序。需要注意的一个小细节:此代码还检查相同类型的两个变量,因此“3”与 3 不同。

5赞 Egor Litvinchuk 11/16/2016 #17

只是想利用一些 es6 功能贡献我的对象比较版本。它不考虑订单。将所有 if/else 转换为三元后,我得到了以下内容:

function areEqual(obj1, obj2) {

    return Object.keys(obj1).every(key => {

            return obj2.hasOwnProperty(key) ?
                typeof obj1[key] === 'object' ?
                    areEqual(obj1[key], obj2[key]) :
                obj1[key] === obj2[key] :
                false;

        }
    )
}
12赞 Zac 3/6/2017 #18

我不知道是否有人发布过类似的东西,但这是我用来检查对象等式的函数。

function objectsAreEqual(a, b) {
  for (var prop in a) {
    if (a.hasOwnProperty(prop)) {
      if (b.hasOwnProperty(prop)) {
        if (typeof a[prop] === 'object') {
          if (!objectsAreEqual(a[prop], b[prop])) return false;
        } else {
          if (a[prop] !== b[prop]) return false;
        }
      } else {
        return false;
      }
    }
  }
  return true;
}

此外,它是递归的,所以它也可以检查深度相等性,如果你这样称呼它的话。

评论

0赞 Hith 8/25/2018
小更正:在遍历 a 和 b 中的每个道具之前,添加此检查 if(Object.getOwnPropertyNames(a).length !== Object.getOwnPropertyNames(b).length ) return false
2赞 canbax 1/17/2019
很明显,适当的相等检查器必须是递归的。我认为这样的递归答案之一应该是正确的答案。接受的答案没有给出代码,也无济于事
24赞 Pratik Bhalodiya 10/9/2017 #19

最简单合乎逻辑的解决方案,用于比较所有内容,如对象、数组、字符串、整数......

JSON.stringify({a: val1}) === JSON.stringify({a: val2})

注意:

  • 您需要替换 和 替换为您的对象val1val2
  • 对于对象,您必须对两端对象进行递归排序(按键)

评论

12赞 Bram Vanroy 12/21/2017
我假设这在许多情况下是行不通的,因为对象中键的顺序无关紧要 - 除非按字母顺序重新排序?(我找不到记录JSON.stringify
0赞 Pratik Bhalodiya 12/29/2017
是的,你是对的......对于对象,必须对两端对象进行递归排序
6赞 Nate-Bit Int 5/25/2018
这不适用于具有循环引用的对象
4赞 Bentaiba Miled Basma 3/21/2018 #20

您可以从下划线 .js 库中使用。_.isEqual(obj1, obj2)

下面是一个示例:

var stooge = {name: 'moe', luckyNumbers: [13, 27, 34]};
var clone  = {name: 'moe', luckyNumbers: [13, 27, 34]};
stooge == clone;
=> false
_.isEqual(stooge, clone);
=> true

从这里查看官方文档:http://underscorejs.org/#isEqual

3赞 Zameer Ansari 5/17/2018 #21

假设对象中属性的顺序没有改变。

JSON.stringify() 适用于深层和非深层两种类型的对象,不太确定性能方面:

var object1 = {
  key: "value"
};

var object2 = {
  key: "value"
};

var object3 = {
  key: "no value"
};

console.log('object1 and object2 are equal: ', JSON.stringify(object1) === JSON.stringify(object2));

console.log('object2 and object3 are equal: ', JSON.stringify(object2) === JSON.stringify(object3));

评论

1赞 SpeedOfRound 11/24/2018
这不会做 OP 想要的,因为只有当两个对象都具有相同的键时,它才会匹配,他们声明他们不会。它还要求密钥的顺序相同,这也不是真正合理的。
3赞 Vishal Sakaria 3/7/2019
属性是什么,顺序不同???不,不是一个好方法
37赞 Vaelin 1/23/2019 #22

对于那些使用 Node 的人来说,有一种方便的方法可以在本机库上调用来实现这一点。isDeepStrictEqualutil

const util = require('util');

const obj1 = {
  foo: "bar",
  baz: [1, 2]
};

const obj2 = {
  foo: "bar",
  baz: [1, 2]
};


obj1 == obj2 // false
util.isDeepStrictEqual(obj1, obj2) // true

https://nodejs.org/api/util.html#util_util_isdeepstrictequal_val1_val2

评论

0赞 TrickOrTreat 1/23/2020
它的性能应该是好的。不用担心。即使我也在复杂的场景中使用它。当我们使用它时,我们不必担心对象的属性是承载 Object 还是数组。无论如何,Json.Stringify 都会让它成为字符串,在 javascript 中比较字符串没什么大不了的
0赞 Viet 10/2/2020
谢谢你的回答!它已添加到 Node v9 中
8赞 Adriano Spadoni 9/25/2019 #23

ES6:我可以完成的最小代码是这个。它通过字符串化所有键值数组排序来递归地进行深度比较,唯一的限制是没有方法或符号进行比较。

const compareObjects = (a, b) => { 
  let s = (o) => Object.entries(o).sort().map(i => { 
     if(i[1] instanceof Object) i[1] = s(i[1]);
     return i 
  }) 
  return JSON.stringify(s(a)) === JSON.stringify(s(b))
}

console.log(compareObjects({b:4,a:{b:1}}, {a:{b:1},b:4}));

重要:这个函数在一个 ARRAY 中执行 JSON.stringfy,其中键已排序,而不是在它本身的对象中:

  1. [“a”, [“b”, 1]]
  2. [“b”, 4]

评论

0赞 digitai 4/1/2020
这是一个功能齐全@Adriano答案,感谢 Spadoni。您知道如何获取已修改的键/属性吗?谢谢
1赞 Adriano Spadoni 4/2/2020
嗨,@digital,如果您需要哪些键不同,这不是理想的功能。检查另一个答案,并使用一个循环遍历对象的答案。
0赞 Eksapsy 11/9/2020
永远不要使用 JSON.stringify 来比较 json 对象。密钥的顺序预计不会相同。
0赞 Adriano Spadoni 11/12/2020
嗨,@Eksapsy,这就是为什么有一个“sort()”函数,你看到 s(a) s(b) 了吗?由于排序,此功能很好。该示例故意在不同的其他环境中使用密钥,以证明它有效。
0赞 Adriano Spadoni 11/12/2020
@digital,要获得差异,需要一个正则表达式而不是“===”,这是可行的。
21赞 Aqeel Bahoo 4/10/2020 #24
var object1 = {name: "humza" , gender : "male", age: 23}
var object2 = {name: "humza" , gender : "male", age: 23}
var result = Object.keys(object1).every((key) =>  object1[key] === object2[key])

如果 object1 在 object2 上具有相同的值,则结果将为 true

评论

5赞 Ram Kumar 6/8/2020
如果 object2 具有 object1 不包含的其他键,则此操作将不起作用。
0赞 Blue Bot 7/4/2021
正如 Kumar 所提到的@Ram这只有在您循环两个对象时才有效,效率不是很高,但对于小对象,我认为字符串化对象会更快 - 虽然不是 100% 确定
0赞 Adrian P. 11/25/2021
@RamKumar只需添加条件 var result = Object.keys(object1).every(((key) =>(object1[key] === object2[key] && object1.hasOwnProperty(key) && object2.hasOwnProperty(key))
3赞 Sergiy Seletskyy 4/23/2020 #25

如何确定分部对象 (Partial<T>) 是否等于打字稿中的原始对象 (T)。

function compareTwoObjects<T>(original: T, partial: Partial<T>): boolean {
  return !Object.keys(partial).some((key) => partial[key] !== original[key]);
}

P.S. 最初,我打算创建一个带有答案的新问题。但是这样的问题已经存在并被标记为重复。

26赞 Aidin 8/20/2020 #26

这个问题已经有 30 多个答案。我将总结和解释它们(用“我父亲”的类比)并添加我建议的解决方案。

您有 4+1 类解决方案


1)使用hacky不完整的快速单行

如果您赶时间并且 99% 的正确性有效,那就太好了。

例如,Pratik BhalodiyaJoel Anair 或 或其他方法将对象转换为 String,然后使用逐个字符比较两个 String。JSON.stringify()JSON.encode.toString()===

但是,缺点是 String 中没有 Object 的全局标准唯一表示形式。例如: 并且是平等的。{ a: 5, b: 8}{b: 8 and a: 5 }

  • 优点:快,快。
  • 缺点: 希望有效!如果环境/浏览器/引擎记住了对象的顺序(例如 Chrome/V8)并且键的顺序不同,它将不起作用(感谢 Eksapsy。所以,完全不能保证。在大型物体中,性能也不会很好。

我父亲的比喻

当我说到我的父亲时,“我高大帅气的父亲”和“我帅气的高个子父亲”是同一个人!但是这两个字符串是不一样的。

请注意,英语语法中实际上有一个正确的(标准方式)形容词顺序,它说它应该是一个“英俊的高个子”,但如果你盲目地假设iOS 8 Safari的Javascript引擎也盲目地遵守相同的语法,你就是在冒着能力的风险!#WelcomeToJavascriptNonStandards


2)编写自己的DIY递归函数

如果你正在学习,那就太好了。

例如,atmin的解决方案

最大的缺点是你肯定会错过一些边缘情况。您是否考虑过对象值中的自引用?你考虑过吗?您是否考虑过两个具有相同但不同原型父级的对象?NaNownProperties

我只鼓励人们在练习并且代码不会投入生产时这样做。这是重新发明轮子的唯一理由。

  • 优点:学习机会。
  • 缺点:不可靠。需要时间和顾虑。

我父亲的比喻

这就像假设如果我父亲的名字是“约翰·史密斯”,他的生日是“1970 年 1 月 1 日”,那么任何名字是“约翰·史密斯”并且出生于“1970 年 1 月 1 日”的人都是我的父亲。

通常情况就是这样,但如果那天有两个“约翰·史密斯”出生呢?如果您认为您会考虑他们的身高,那么这提高了准确性,但仍然不是一个完美的比较。

2.1 你有限范围的DIY比较器

与其疯狂地以递归方式检查所有属性,不如考虑只检查“有限”数量的属性。例如,如果对象是 s,则可以比较它们的字段。UseremailAddress

它仍然不是一个完美的解决方案,但与解决方案#2相比,其好处是:

  1. 这是可以预测的,而且不太可能崩溃。
  2. 您正在推动相等的“定义”,而不是依赖于对象及其原型和嵌套属性的野生形式和形状。

3) 使用函数的库版本equal

如果您需要生产级质量,并且不能更改系统的设计,则很好。

例如,lodash 已经在 coolaj86 的答案中,或者 Tony Harvey 的答案或 Rafael Xavier 的 Node 中提到的 Angular 或 Ember 的答案中。_.equal

  • 优点:这是其他人所做的。
  • 缺点:外部依赖性,这可能会花费您额外的内存/CPU/安全问题,即使是一点点。此外,仍然可能遗漏一些边缘情况(例如,是否应将具有相同但不同原型父级的两个对象视为相同。最后,你可能会无意中用创可贴一个潜在的设计问题;只是说说而已!ownProperties

我父亲的比喻

这就像付钱给中介公司,根据他的电话、姓名、地址等来寻找我的亲生父亲。

这将花费更多,而且可能比我自己进行背景调查更准确,但不包括边缘情况,例如我父亲是移民/庇护者,他的生日未知!


4) 在对象中使用标识符

如果你[仍然]可以改变系统的设计(你正在处理的对象),并且你希望你的代码持续很长时间,那就太好了。

它不适用于所有情况,并且可能不是很高性能。但是,如果您能做到,这是一个非常可靠的解决方案。

解决方案是,系统中的每个属性都将具有唯一标识符以及所有其他属性。标识符的唯一性将在生成时得到保证。在比较两个对象时,您将使用此 ID(也称为 UUID/GUID - 全局/通用唯一标识符)。即,当且仅当这些 ID 相等时,它们是相等的。object

ID 可以是简单的数字,也可以是通过生成的字符串(建议)或一段代码。您需要做的就是确保它始终是唯一的,如果它可以内置,或者在UUID的情况下,可以检查所有现有值(例如MySQL的列属性)或简单地(如果来自库)是否依赖于冲突的可能性极低。auto_incrementalauto_incrementalUNIQUE

请注意,您还需要始终将 ID 与对象一起存储(以保证其唯一性),并且实时计算它可能不是最佳方法。

  • 优点:可靠、高效、不脏、现代。
  • 缺点:需要额外的空间。可能需要重新设计系统。

我父亲的比喻

就像知道我父亲的社会安全号码是 911-345-9283,所以任何拥有这个 SSN 的人都是我的父亲,任何自称是我父亲的人都必须拥有这个 SSN。


结论

我个人更喜欢解决方案#4(ID),而不是它们,因为它的准确性和可靠性。如果不可能,我会选择#2.1以获得可预测性,然后是#3。如果两者都不可能,则#2,最后是#1。

评论

2赞 Eksapsy 10/25/2020
当对象的顺序不同时,第一个“hacky”解决方案也根本不起作用。例如。 -比较o1 = { a: '1', b: '2' }o2 = { b: '2', a: '1' }JSON.stringify(o1) === JSON.stringify(o2) = false
0赞 Dave F 12/22/2021
首选方法假定对象具有特定用途,例如作为唯一对象的数据集,而不是其他用途,例如是一组重复的项,其中键/属性是项,值是项在集合中的次数(这需要检查每个属性和值)。
0赞 Aidin 12/23/2021
@DaveF在这种情况下,从语义上讲,也许 Map 比野生对象更合适。
0赞 Dave F 12/23/2021
@Aidin 我的观点是,有时需要检查所有属性和值。这个例子并不重要。
6赞 Paul 4/17/2021 #27

下面是一个简短的实现,它使用但@Jor此处建议的键进行排序。JSON.stringify

一些测试是从这里@EbrahimByagowi的答案中获取的。

当然,通过使用 ,解决方案仅限于 JSON 可序列化类型(字符串、数字、JSON 对象、数组、布尔值、null)。不支持 、 等对象。JSON.stringifyDateFunction

function objectEquals(obj1, obj2) {
  const JSONstringifyOrder = obj => {
    const keys = {};
    JSON.stringify(obj, (key, value) => {
      keys[key] = null;
      return value;
    });
    return JSON.stringify(obj, Object.keys(keys).sort());
  };
  return JSONstringifyOrder(obj1) === JSONstringifyOrder(obj2);
}

///////////////////////////////////////////////////////////////
/// The borrowed tests, run them by clicking "Run code snippet"
///////////////////////////////////////////////////////////////
var printResult = function (x) {
    if (x) { document.write('<div style="color: green;">Passed</div>'); }
    else { document.write('<div style="color: red;">Failed</div>'); }
};
var assert = { isTrue: function (x) { printResult(x); }, isFalse: function (x) { printResult(!x); } }

assert.isTrue(objectEquals("hi","hi"));
assert.isTrue(objectEquals(5,5));
assert.isFalse(objectEquals(5,10));

assert.isTrue(objectEquals([],[]));
assert.isTrue(objectEquals([1,2],[1,2]));
assert.isFalse(objectEquals([1,2],[2,1]));
assert.isFalse(objectEquals([1,2],[1,2,3]));

assert.isTrue(objectEquals({},{}));
assert.isTrue(objectEquals({a:1,b:2},{a:1,b:2}));
assert.isTrue(objectEquals({a:1,b:2},{b:2,a:1}));
assert.isFalse(objectEquals({a:1,b:2},{a:1,b:3}));

assert.isTrue(objectEquals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}},{1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}));
assert.isFalse(objectEquals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}},{1:{name:"mhc",age:28}, 2:{name:"arb",age:27}}));

3赞 iamabs2001 9/22/2021 #28
let std1 = {
  name: "Abhijeet",
  roll: 1
}

let std2 = {
  name: "Siddharth",
  roll: 2
}

console.log(JSON.stringify(std1) === JSON.stringify(std2))

评论

2赞 Philzen 7/15/2022
⚠️JSON.stringify({y: 1, x: 1}) !== JSON.stringify({x: 1, y: 1})
4赞 Christopher Silvas 2/1/2022 #29

我发现在忽略属性顺序的同时比较两个 javascript 对象的的一种简单方法是使用 JSON stringify 替换器函数:

const compareReplacer = (key, value) => {
    if(typeof value === 'object' && !(value instanceof Array))
        return Object.entries(value).sort();
    return value;
}
export const compareObjects = (a, b) => JSON.stringify(a, compareReplacer) === JSON.stringify(b, compareReplacer);

这将在每一步对属性进行排序,以便字符串结果将不变于属性顺序。有些人以前可能做过这个,但我只是想我会分享它,以防万一:)。

3赞 Haseeb Butt 2/3/2022 #30

字符串化两个对象并进行比较

return (JSON.stringify(obj1) === JSON.stringify(obj2))

这将返回 true 或 false

评论

0赞 Linden X. Quan 3/14/2022
ob1 = {“age”: 1, “name”: “a” } ob2 = {“name”: “a”, “age”: 1} JSON.stringify(ob1) == JSON.stringify(ob2) // 假
9赞 thelearner 5/19/2022
属性的顺序很重要
0赞 kodmanyagha 1/24/2023
解决方案很简单,但并不有效。