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


669

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

堆栈溢出问题JavaScript中是否存在某种hashCode函数?与这个问题相似,但需要更多的学术答案。上面的场景演示了为什么必须要有一个,而我想知道是否有任何等效的解决方案



37
需要注意的是,即使是在Java中,a.hashCode() == b.hashCode()没有暗示a等于b。这是必要条件,不是充分条件。
Heinzi 2014年


2
如果您必须比较代码中的对象,则可能是您编写的代码错误。更好的问题可能是:“如何编写这段代码,这样我就不必比较对象了?”
th317erd

3
@ th317erd您能解释一下吗?...
El Mac

Answers:


175

简短的答案

简单的答案是:不,没有一种通用的方法可以确定一个对象是否等同于您的意思。例外是当您严格考虑对象是无类型的。

长答案

该概念是Equals方法的概念,该方法比较对象的两个不同实例以指示它们在值级别上是否相等。但是,取决于特定类型来定义Equals应如何实施方法。具有原始值的属性的迭代比较可能不够,可能存在一些不被视为对象值一部分的属性。例如,

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

在上述情况下,c确定MyClass的任何两个实例是否相等,仅ab是否重要并不重要。在某些情况下c,实例之间可能会有所不同,但在比较期间并不明显。

请注意,当成员本身也可能是类型的实例,并且每个成员都必须具有确定相等性的方法时,就会出现此问题。

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

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

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

如前所述,该异常将是严格无类型的对象。在这种情况下,唯一明智的选择是每个成员的迭代和递归比较。即使这样,也必须问一个函数的“值”是什么?


175
如果您使用的是下划线,则可以这样做_.isEqual(obj1, obj2);
chovy

12
@Harsh,答案没有给出任何解决方案,因为没有答案。即使在Java中,也没有实现对象相等性比较的灵丹妙药,并且正确实现该.equals方法也不是一件容易的事,这就是为什么在Effective Java中有这样一个主题的原因。
lcn

3
@Kumar Harsh,使两个对象相等的原因是特定于应用程序的;并非一定要考虑对象的每个属性,因此强行强制对象的每个属性也不是具体的解决方案。
sethro 2014年

googled javascript equality object,得到tl; dr的回复,从@chovy评论中选择了一条。谢谢
Andrea

如果使用角度,您将拥有angular.equals
boatcoder 2014年

506

为什么要重新发明轮子?给Lodash一试。它具有许多必备功能,例如isEqual()

_.isEqual(object, other);

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

注:以前这个答案推荐Underscore.js,但lodash做得越来越修复的错误,并与一致性解决问题的一个更好的工作。


27
Underscore的isEqual函数非常好(但是您必须拉入他们的库才能使用它-压缩3K左右)。
mckoss

29
如果您看看其他强调给您的内容,您将不会后悔
PandaWood 2012年

6
即使您不能将下划线作为依赖项,也请拉出isEqual函数,满足许可证要求,然后继续。这是迄今为止关于stackoverflow提到的最全面的平等测试。
戴尔·安德森

7
Underscore有一个名为LoDash的分支,该作者非常关心诸如此类的一致性问题。使用LoDash进行测试,看看会得到什么。
CoolAJ86

6
@mckoss,如果您不想使用整个库npmjs.com/package/lodash.isequal,
Rob Fox,

161

当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

如果可以将对象转换为JSON字符串,那么它将使equals()函数变得简单。
斯科特,2012年

3
@scotts并非总是如此。对于紧密循环中的复杂对象,将对象转换为JSON并比较字符串可能会占用大量计算资源。对于简单的对象,它可能并不重要,但实际上,它确实取决于您的特定情况。正确的解决方案可能只是比较对象ID或检查每个属性一样简单,但是其正确性完全由问题域决定。
Daniel X Moore

我们是否也应该比较数据类型?返回other.rank === this.rank && other.suit === this.suit;
2013年

1
@devsathish可能不会。在JavaScript中,类型非常快速且松散,但是如果在您的域中类型很重要,那么您可能还需要检查类型。
Daniel X Moore

7
@scotts转换为JSON的另一个问题是字符串中属性的顺序变得很重要。{x:1, y:2}!=={y:2, x:1}
Stijn de Witt

81

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

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


66

这是我的版本。它使用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 } }));


objectEquals([1,2,undefined],[1,2])返回true
Roy Tinker 2014年

objectEquals([1,2,3],{0:1,1:2,2:3})还返回true-例如,没有类型检查,只有键/值检查。
罗伊·廷克

objectEquals(new Date(1234),1234)返回true
Roy Tinker

1
if(x.constructor!== y.constructor){返回false; 在不同窗口中比较两个'new String('a')'时,这会中断。对于值相等,您必须检查两个对象上是否都为String.isString,然后使用宽松的相等检查'a == b'。
Triynko

1
“值”相等和“严格”相等之间存在巨大差异,因此不应以相同的方式实现它们。除了基本结构(这是以下4个之一)之外,值相等不关心类型:“对象”(即键/值对的集合),“数字”,“字符串”或“数组”。而已。任何非数字,字符串或数组的内容都应作为一组键/值对进行比较,而不管构造函数是什么(跨窗口安全)。比较对象时,将文字数字和Number的实例的值相等,但不要将字符串强制为数字。
Triynko

50

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

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

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

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


91
有趣,但在我看来有点棘手。例如,您能否100%保证将始终以相同的顺序生成对象属性?
Guido

25
这是一个很好的问题,并提出了另一个问题,即具有相同属性但顺序不同的两个对象是否真正相等。我想这取决于您的意思。
乔尔·安奈尔

11
请注意,大多数编码器和字符串化器会忽略函数,并将非限定数字(如NaN)转换为null。
Stephen Belanger

4
我同意Guido的观点,属性的顺序很重要,无法保证。@JoelAnair,我认为如果属性的值相等,则具有相同属性但顺序不同的两个对象应视为相等。
Juzer Ali 2012年

5
可以与备用JSON字符串化器一起使用,该字符串化器始终对对象键进行排序。
罗伊·廷克

40

简短的功能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,使用臂架的建议和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);
}

5
你可以替换reduceevery简化。
三角帆

1
@nonkertompf确定他可以:Object.keys(x).every(key => deepEqual(x[key], y[key]))
三角帆

2
当您比较两个日期时,此操作将失败
Greg

3
deepEqual({},[])返回true
AlexMorley-Finch

3
是的,如果您在乎这种: (x === y): (x === y && (x != null && y != null || x.constructor === y.constructor))
极端

22

您是否要测试两个对象是否相等?即:它们的属性是否相等?

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

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()函数可能是可行的方法。


这是一个很长的方法,但是它可以完全测试对象,而无需对每个对象中的属性顺序进行任何假设。
briancollins081

22

如果方便使用深层复制功能,则可以在匹配属性顺序时使用以下技巧继续使用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/

理由:

由于的属性obj1会一一复制到克隆中,因此将保留其在克隆中的顺序。而且,当将的属性obj2复制到克隆中时,由于已经存在的属性obj1将被简单地覆盖,因此它们在克隆中的顺序将被保留。


11
我认为跨浏览器/引擎无法保证订单保存。
2013年

我需要@JoLiss引用;)我记得在多个浏览器中对此进行测试,得到一致的结果。但是,当然,没有人可以保证将来的浏览器/引擎中的行为保持不变。充其量,这是一个技巧(正如答案中已经提到的那样),但我并不是说它是比较对象的可靠方法。
Ates Goral

1
当然,这里有一些提示:ECMAScript规范说对象是“无序的”;而这个答案对当前浏览器的实际发散行为。
2013年

2
@JoLiss谢谢!但是请注意,我从来没有要求保留代码和编译对象之间的顺序。我声称保留其值被就地替换的属性顺序。这就是我的解决方案的关键:使用mixin来覆盖属性值。假设实现通常选择使用某种哈希图,则仅替换值应保留键的顺序。实际上,正是我在不同的浏览器中测试过的。
Ates Goral

1
@AtesGoral:是否可以使此约束更明确(粗体,...)。大多数人只是简单地执行复制粘贴操作,而不阅读周围的文本...
Willem Van Onsem 2015年

21

在Node.js中,您可以使用其native require("assert").deepStrictEqual。更多信息:http : //nodejs.org/api/assert.html

例如:

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

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

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

Chai也具有此功能。在这种情况下,您将使用:var foo = { a: 1 }; var bar = { a: 1 }; expect(foo).to.deep.equal(bar); // true;
Folusho Oladipo

某些版本的Node.js设置error.name"AssertionError [ERR_ASSERTION]"。在这种情况下,我将if语句替换为if (error.code === 'ERR_ASSERTION') {
Knute Knudsen

我不知道deepStrictEqual要走的路。我一直在想办法弄清楚为什么strictEqual不起作用。太棒了
NetOperator Wibby

19

比较对象,数组,字符串,整数等所有内容的最简单逻辑的解决方案

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

注意:

  • 您需要替换val1val2用您的对象
  • 对于对象,您必须对两个侧面对象进行递归排序(按键)

6
我假设这在许多情况下都行不通,因为对象中键的顺序无关紧要-除非JSON.stringify按字母顺序重新排序?(我找不到记录的文件。)
Bram Vanroy

是的,您是对的...对于对象,您必须对两个侧面对象进行递归排序
Pratik Bhalodiya 17/12/29

2
这不适用于具有循环引用的对象
Nate-Bit Int

13

我使用此comparable函数来生成与JSON可比的对象副本:

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
好主意(在我的情况下,要比较的对象只是键/值对,没有特殊的东西)
2016年

10

以下是使用功能样式方法的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;
  }
}

演示在这里


如果对象键的顺序已更改,则无法使用。
Isaac Pak

9

我不知道是否有人发布过与此类似的内容,但这是我用来检查对象相等性的函数。

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

而且,它是递归的,因此它也可以检查深度相等性(如果您称之为它)。


小修正:在通过a和b中的每个道具之前,添加此检查if(Object.getOwnPropertyNames(a).length!== Object.getOwnPropertyNames(b).length)返回false
Hith

1
显然,适当的相等性检查器必须是递归的。我认为这样的递归答案之一应该是正确的答案。接受的答案不会给出代码,也没有帮助
canbax

9

对于那些使用NodeJS的用户,有一个便捷的方法可以isDeepStrictEqual在本机Util库上调用,以实现此目的。

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


它的性能应该是好的。别担心。甚至我也在复杂的场景中使用它。当我们使用它时,我们不必担心对象的属性是承载对象还是数组。Json.Stringify仍然使它成为字符串,并且在javascript中比较字符串不是什么大问题
TrickOrTreat

6

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}));


感谢@Adriano Spadoni,这是一个功能齐全的答案。您知道如何获取已修改的密钥/属性吗?谢谢,
digitai

1
@digital,您好,如果您需要不同的键,这不是理想的功能。检查另一个答案,并在对象之间循环使用。
Adriano Spadoni

5

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

这是一个例子:

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


5

假定对象中属性的顺序未更改。

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
这并不能满足OP的要求,因为只有两个对象都具有相同的键(它们声明不会)时,它才会匹配。这也将要求密钥具有相同的顺序,这也不是很合理。
SpeedOfRound

1
什么是属性按不同顺序排列?不,不是一个好方法
Vishal Sakaria

4

许多人没有意识到的简单解决方案是对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(''));
}

关于这种方法的另一个有用的东西是你可以通过一个“替代品”功能的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
哦,我也忘记了,但是如果它们是同一个对象,则可以通过先测试对象相等并尽快进行验证来加快该功能:if(obj1 === obj2)返回true;
th317erd 2015年

11
areEqual({a: 'b'}, {b: 'a'})得到true呢?
okm 2015年

是的,发布后我意识到此“解决方案”存在问题。要使排序算法真正正常工作,还需要做更多的工作。
15th

4

只是想利用某些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;

        }
    )
}

3

需要比发布的对象更通用的对象比较功能,我准备了以下内容。批评赞赏...

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
我最终使用了这种形式。感谢您计数成员的想法!
NateS

2
修改时要非常小心Object.prototype-在大多数情况下,建议不要修改(例如,所有for..in循环中都会出现添加项)。也许考虑Object.equals = function(aObj, bObj) {...}
罗伊·廷克

3

如果要比较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 } }

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


3

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

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函数本身和一个用于在数组/对象中查找属性的数字索引的函数。因为indexof仅查找数字和字符串,而没有对象,所以仅需要find函数。

可以这样称呼它:

({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不同。


3

我看到了意大利面的代码答案。不使用任何第三方库,这很容易。

首先,通过键的键名对两个对象进行排序。

let objectOne = { hey, you }
let objectTwo = { you, hey }

// If you really wanted you could make this recursive for deep sort.
const sortObjectByKeyname = (objectToSort) => {
    return Object.keys(objectToSort).sort().reduce((r, k) => (r[k] = objectToSort[k], r), {});
}

let objectOne = sortObjectByKeyname(objectOne)
let objectTwo = sortObjectByKeyname(objectTwo)

然后只需使用字符串进行比较即可。

JSON.stringify(objectOne) === JSON.stringify(objectTwo)

这对于深度复制也不起作用,它只有一个迭代深度。
安德拉斯

我认为@andras意味着您需要对嵌套对象的键进行递归排序。
Davi Lima

2

我建议不要进行散列或序列化(如JSON解决方案所示)。如果需要测试两个对象是否相等,则需要定义相等的含义。可能是两个对象中的所有数据成员都匹配,或者可能是内存位置必须匹配(这意味着两个变量都引用了内存中的同一对象),或者可能是每个对象中只有一个数据成员必须匹配。

最近,我开发了一个对象,该对象的构造函数在每次创建实例时都会创建一个新的id(从1开始并以1递增)。该对象具有isEqual函数,该函数将该ID值与另一个对象的ID值进行比较,如果匹配则返回true。

在那种情况下,我将“等于”定义为id值匹配。假定每个实例都有一个唯一的ID,则可以用来执行这样的想法,即匹配对象也占据相同的内存位置。虽然这不是必需的。


2

如果两个对象的所有属性都具有相同的值,并且对于所有嵌套的对象和数组具有递归值,则认为两个对象相等是很有用的。我还认为以下两个对象相等:

var a = {p1: 1};
var b = {p1: 1, p2: undefined};

同样,数组可以具有“缺失”元素和未定义元素。我也将同样对待:

var c = [1, 2];
var d = [1, 2, undefined];

实现此相等性定义的函数:

function isEqual(a, b) {
    if (a === b) {
        return true;
    }

    if (generalType(a) != generalType(b)) {
        return false;
    }

    if (a == b) {
        return true;
    }

    if (typeof a != 'object') {
        return false;
    }

    // null != {}
    if (a instanceof Object != b instanceof Object) {
        return false;
    }

    if (a instanceof Date || b instanceof Date) {
        if (a instanceof Date != b instanceof Date ||
            a.getTime() != b.getTime()) {
            return false;
        }
    }

    var allKeys = [].concat(keys(a), keys(b));
    uniqueArray(allKeys);

    for (var i = 0; i < allKeys.length; i++) {
        var prop = allKeys[i];
        if (!isEqual(a[prop], b[prop])) {
            return false;
        }
    }
    return true;
}

源代码(包括帮助函数,generalType和uniqueArray): 此处是单元测试测试运行器


2

我使用此函数进行以下假设:

  1. 您可以控制要比较的对象,并且只有原始值(即没有嵌套的对象,函数等)。
  2. 您的浏览器支持Object.keys

这应该被视为简单策略的演示。

/**
 * Checks the equality of two objects that contain primitive values. (ie. no nested objects, functions, etc.)
 * @param {Object} object1
 * @param {Object} object2
 * @param {Boolean} [order_matters] Affects the return value of unordered objects. (ex. {a:1, b:2} and {b:2, a:1}).
 * @returns {Boolean}
 */
function isEqual( object1, object2, order_matters ) {
    var keys1 = Object.keys(object1),
        keys2 = Object.keys(object2),
        i, key;

    // Test 1: Same number of elements
    if( keys1.length != keys2.length ) {
        return false;
    }

    // If order doesn't matter isEqual({a:2, b:1}, {b:1, a:2}) should return true.
    // keys1 = Object.keys({a:2, b:1}) = ["a","b"];
    // keys2 = Object.keys({b:1, a:2}) = ["b","a"];
    // This is why we are sorting keys1 and keys2.
    if( !order_matters ) {
        keys1.sort();
        keys2.sort();
    }

    // Test 2: Same keys
    for( i = 0; i < keys1.length; i++ ) {
        if( keys1[i] != keys2[i] ) {
            return false;
        }
    }

    // Test 3: Values
    for( i = 0; i < keys1.length; i++ ) {
        key = keys1[i];
        if( object1[key] != object2[key] ) {
            return false;
        }
    }

    return true;
}

2

这是以上所有内容的补充,而不是替代。如果您需要快速浅比较对象,而无需检查额外的递归情况。这是一个镜头。

相比之下:1)拥有的属性数量相等,2)密钥名称相等,3)如果bCompareValues == true,则对应的属性值及其类型相等(三重相等)

var shallowCompareObjects = function(o1, o2, bCompareValues) {
    var s, 
        n1 = 0,
        n2 = 0,
        b  = true;

    for (s in o1) { n1 ++; }
    for (s in o2) { 
        if (!o1.hasOwnProperty(s)) {
            b = false;
            break;
        }
        if (bCompareValues && o1[s] !== o2[s]) {
            b = false;
            break;
        }
        n2 ++;
    }
    return b && n1 == n2;
}

2

为了比较简单键/值对对象实例的键,我使用:

function compareKeys(r1, r2) {
    var nloops = 0, score = 0;
    for(k1 in r1) {
        for(k2 in r2) {
            nloops++;
            if(k1 == k2)
                score++; 
        }
    }
    return nloops == (score * score);
};

比较密钥后,一个简单的附加for..in循环就足够了。

复杂度为O(N * N),其中N为键的数量。

我希望/猜测我​​定义的对象不会包含超过1000个属性...


2

我知道这有点老了,但我想添加一个针对此问题的解决方案。我有一个对象,想知道其数据何时更改。“类似于Object.observe”,我所做的是:

function checkObjects(obj,obj2){
   var values = [];
   var keys = [];
   keys = Object.keys(obj);
   keys.forEach(function(key){
      values.push(key);
   });
   var values2 = [];
   var keys2 = [];
   keys2 = Object.keys(obj2);
   keys2.forEach(function(key){
      values2.push(key);
   });
   return (values == values2 && keys == keys2)
}

在这里可以复制它,并创建另一组数组以比较值和键。这非常简单,因为它们现在是数组,并且如果对象的大小不同,则将返回false。


1
这将始终返回false,因为数组不按值进行比较,例如[1,2] != [1,2]
三角帆
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.