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

Posted

技术标签:

【中文标题】如何确定两个 JavaScript 对象的相等性?【英文标题】:How to determine equality for two JavaScript objects? 【发布时间】:2010-09-17 02:27:21 【问题描述】:

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

Stack Overflow 问题Is there any kind of hashCode function in javascript? 与此问题类似,但需要更学术的答案。上面的场景说明了为什么有必要拥有一个,我想知道是否有任何等效解决方案

【问题讨论】:

也看看这个问题***.com/q/1068834/1671639 请注意,即使在 Java 中,a.hashCode() == b.hashCode() 确实暗示a 等于b。这是必要条件,不是充分条件。 如果您必须比较代码中的对象,那么您可能写错了代码。更好的问题可能是:“我怎样才能编写这段代码,这样我就不必比较对象了?” @th317erd 你能解释一下吗?... @ElMac 我不能直接代表这个人,但我确实同意这种说法,我的思考过程是 JS 对象通常相当大。你很少有像person = name: "fred", age: 42 这样的属性很少的东西。如果你这样做并且你必须通过完全相等来搜索它们,这似乎是一种浪费。大多数情况下,你的普通对象仍然有很多属性——其中一个是唯一的,或者你希望一个是唯一的,例如某种 ID。然后,您可以按此进行搜索,而无需检查是否每件事都匹配。 【参考方案1】:

为什么要重新发明***?试试Lodash。它有很多必备功能,例如isEqual()。

_.isEqual(object, other);

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

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

【讨论】:

Underscore 的 isEqual 函数非常好(但你必须拉入他们的库才能使用它 - 大约 3K gzipped)。 即使您不能承受下划线作为依赖项,也请退出 isEqual 函数,满足许可要求并继续前进。这是迄今为止在 *** 上提到的最全面的平等测试。 Underscore 有一个分支,叫做LoDash,作者非常关心诸如此类的一致性问题。使用 LoDash 进行测试,看看你会得到什么。 @mckoss 如果您不想要整个库,可以使用独立模块npmjs.com/package/lodash.isequal 怎么说“哦,就用X包!”有帮助吗?您是在告诉人们抽象和批量化他们的代码,而没有真正解释如何自己实际找到解决方案。我并不是说不要使用包或建议它们,但 JavaScript 生态系统是善变的,你应该促进对解决方案的实际理解,而不仅仅是一种暂时绕过它的方法。【参考方案2】:

简短回答

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

长答案

这个概念是 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 中,数据和方法之间的区别是模糊的。

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

如果一个对象的一个​​现有原型方法被另一个函数覆盖了呢?是否仍可以认为它等于另一个相同的实例?这个问题只能在每种类型的特定情况下回答。

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

【讨论】:

如果你使用下划线,你可以这样做_.isEqual(obj1, obj2); @Harsh,答案没有给出任何解决方案,因为没有。即使在 Java 中,也没有灵丹妙药来比较对象相等性,正确实现 .equals 方法并非易事,这就是为什么在 Effective Java 中有这样一个主题。 @Kumar Harsh,使两个对象相等的原因是非常特定于应用程序的;并非必须考虑对象的每个属性,因此暴力破解对象的每个属性也不是一个具体的解决方案。 google了javascript equality object,得到了tl;dr的回复,从@chovy评论中拿了一条线。谢谢 什么是下划线?是图书馆吗?检查对象相等性的最小大小代码 sn-p 是多少?【参考方案3】:

当对象在内存中引用相同的位置时,JavaScript 中默认的相等运算符返回 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() 函数变得简单。 @scotts 并非总是如此。对于紧密循环中的复杂对象,将对象转换为 JSON 并比较字符串可能会变得计算密集。对于简单的对象,它可能并不重要,但实际上它确实取决于您的具体情况。正确的解决方案可能就像比较对象 ID 或检查每个属性一样简单,但其正确性完全取决于问题域。 我们不应该也比较数据类型吗?! return other.rank === this.rank && other.suit === this.suit; @devsathish 可能不会。在 JavaScript 中类型非常快速和松散,但如果在您的域中类型很重要,那么您可能还需要检查类型。 @scotts 转换为 JSON 的其他问题是字符串中属性的顺序变得很重要。 x:1, y:2 !== y:2, x:1【参考方案4】:

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

angular.equals - 有关此方法的更多信息,请参阅 docs 或 source。它也对数组进行了深入比较。 Ember.js isEqual - 有关此方法的更多信息,请参阅 docs 或 source。它不会对数组进行深度比较。

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>

【讨论】:

【参考方案5】:

这是我的版本。它正在使用 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 *** 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 objectEquals([1,2,3],0:1,1:2,2:3) 也返回 true -- 例如没有类型检查,只有键/值检查。 objectEquals(new Date(1234),1234) 返回true if (x.constructor !== y.constructor) return false;当比较不同窗口中的两个 'new String('a')' 时,这会中断。对于值相等,您必须检查两个对象上是否有 String.isString,然后使用松散相等检查 'a == b'。 “值”相等和“严格”相等之间存在巨大差异,它们不应该以相同的方式实现。除了基本结构之外,值相等不应该关心类型,它是以下 4 种之一:“对象”(即键/值对的集合)、“数字”、“字符串”或“数组”。而已。任何不是数字、字符串或数组的东西都应该作为一组键/值对进行比较,无论构造函数是什么(跨窗口安全)。比较对象时,将文字数字的值与 Number 的实例相等,但不要将字符串强制转换为数字。【参考方案6】:

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

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

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

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

【讨论】:

有趣,但在我看来有点棘手。例如,您能否 100% 保证对象属性始终以相同的顺序生成? 这是一个很好的问题,并提出了另一个问题,即两个具有相同属性但顺序不同的对象是否真的相等。取决于你所说的平等,我猜。 请注意,大多数编码器和字符串化器会忽略函数并将非有限数字(如 NaN)转换为 null。 我同意 Guido,属性的顺序很重要,不能保证。 @JoelAnaair,如果属性的值相等,我认为两个具有不同顺序的相同属性的对象应该被认为是相等的。 可以与另一种 JSON 字符串化器一起使用,它可以一致地对对象键进行排序。【参考方案7】:

短功能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);

【讨论】:

您可以将reduce 替换为every 以简化。 @nonkertompf 肯定他可以:Object.keys(x).every(key =&gt; deepEqual(x[key], y[key])). 比较两个日期时失败 deepEqual(, []) 返回 true 是的,如果你关心这种极端情况,丑陋的解决方案是将: (x === y)替换为: (x === y &amp;&amp; (x != null &amp;&amp; y != null || x.constructor === y.constructor))【参考方案8】:

在 Node.js 中,您可以使用其原生的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; 某些版本的 Node.js 将 error.name 设置为 "AssertionError [ERR_ASSERTION]"。在这种情况下,我会将 if 语句替换为 if (error.code === 'ERR_ASSERTION') 我不知道deepStrictEqual 是要走的路。我一直在绞尽脑汁想弄清楚为什么strictEqual 不起作用。太棒了。【参考方案9】:

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

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

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() 函数可能是要走的路。

【讨论】:

这是一个很长的方法,但它完全测试了对象,而不对每个对象中的属性顺序做任何假设。【参考方案10】:

对于那些使用 Node 的人,在 nativeutil 库中有一个名为 isDeepStrictEqual 的便捷方法可以实现此目的。

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 中比较字符串并不是什么大问题 感谢您的回答!它是在 Node v9 中添加的【参考方案11】:

如果你有一个方便的深拷贝功能,你可以使用以下技巧仍然在匹配属性顺序的同时使用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 中已经存在的属性将被简单地覆盖,它们在克隆中的顺序将被保留。

【讨论】:

我不认为跨浏览器/引擎可以保证订单保留。 @JoLiss Citations required ;) 我记得在多个浏览器中对此进行了测试,得到了一致的结果。但当然,没有人能保证在未来的浏览器/引擎中保持相同的行为。这充其量只是一个技巧(正如答案中已经提到的那样),我并不是说它是比较对象的万无一失的方法。 当然,这里有一些建议:ECMAScript spec says object is "unordered";和this answer 在当前浏览器上的实际发散行为。 @JoLiss 谢谢你!但请注意,我从未声称保留代码和编译对象之间的顺序。我声称保留其值被就地替换的属性的顺序。这是我的解决方案的关键:使用 mixin 来覆盖属性值。假设实现通常选择使用某种 hashmap,只替换值应该保留键的顺序。事实上,这正是我在不同浏览器中测试过的。 @AtesGoral: 是否有可能使这个约束更加explicit(粗体,...)。大多数人只是简单地复制粘贴而不阅读它周围的文字......【参考方案12】:

最简单逻辑解决方案,用于比较诸如Object、Array、String、Int...

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

注意:

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

【讨论】:

我假设这在很多情况下都行不通,因为对象中键的顺序无关紧要——除非JSON.stringify 进行字母顺序重新排序? (我找不到documented。) 是的,你是对的......对于对象,你必须对两个侧面对象进行递归排序 这不适用于具有循环引用的对象【参考方案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)));
&lt;div id="div"&gt;&lt;/div&gt;

在测试中派上用场(大多数测试框架都有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.

【讨论】:

好主意(在我的情况下,要比较的对象只是键/值对,没有特殊的东西)【参考方案14】:

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

您有4+1 类解决方案


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

如果你赶时间并且 99% 的正确率有效。

例如,JSON.stringify() 建议使用 by Pratik Bhalodiya 或 JSON.encode by Joel Anair 或 .toString(),或其他将对象转换为字符串然后使用 === 字符比较两个字符串的方法按字符。

然而,缺点是字符串中的对象没有全局标准的唯一表示。例如 a: 5, b: 8b: 8 and a: 5 相等。

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

我父亲的比喻

说到我爸,“我高大帅哥”和“我高大帅爸”是同一个人!但是这两个字符串不一样。

请注意,英语语法中的形容词实际上有一个正确(标准方式)顺序,says 它应该是“英俊的高个子”,但如果您盲目假设ios 8 Safari的Javascript引擎也遵循相同的语法,盲目! #WelcomeToJavascriptNonStandards


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

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

例如atmin's solution。

最大的缺点是你肯定会错过一些边缘情况。您是否考虑过对象值中的self-reference?你考虑过NaN吗?您是否考虑过两个具有相同 ownProperties 但原型父母不同的对象?

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

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

我父亲的比喻

这就像假设如果我父亲的名字是“John Smith”并且他的生日是“1/1/1970”,那么任何名为“John Smith”并且出生于“1970 年 1 月 1 日”的人都是我的父亲.

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

2.1 你有限的范围DIY比较器

与其疯狂地递归检查所有属性,不如考虑只检查“有限”数量的属性。比如对象是Users,你可以比较他们的emailAddress字段。

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

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

3) 使用库版本的equal 函数

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

示例是 _.equal of lodash,已经在 coolaj86's answer 或 Angular 或 Ember 中,如 Tony Harvey's answer 或 Node 的 by Rafael Xavier 中所述。

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

我父亲的比喻

这就像付钱找我的亲生父亲,根据他的电话、姓名、地址等。

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


4) 在对象中使用标识符

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

它并非在所有情况下都适用,并且可能不是很高效。不过,如果你能做到的话,这是一个非常可靠的解决方案。

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

ID 可以是简单的auto_incremental 数字,也可以是通过a library(建议)或a piece of code 生成的字符串。您需要做的就是确保它始终是唯一的,在 auto_incremental 的情况下它可以是内置的,或者在 UUID 的情况下,可以检查所有现有值(例如 mysqlUNIQUE 列属性)或只是(如果来自图书馆)依赖于提供极低的碰撞可能性。

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

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

我父亲的比喻

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


结论

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

【讨论】:

当对象的顺序不同时,第一个“hacky”解决方案也根本不起作用。例如。 o1 = a: '1', b: '2' - o2 = b: '2', a: '1' 比较 JSON.stringify(o1) === JSON.stringify(o2) = false 您的首选方法假定对象具有特定用途,例如作为唯一对象的数据集,而不是另一种用途,例如作为具有重复项的一组项目,其中键/属性是项目,值是项目在集合中的次数(这需要检查每个属性和值)。 @DaveF 在这种情况下,也许Map 在语义上比野生对象更合适。 @Aidin 我的意思是有时需要检查所有属性和值。这个例子并不重要。【参考方案15】:
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

【讨论】:

如果 object2 有 object1 不包含的附加键,这将不起作用。 正如@Ram Kumar 提到的,这只有在你循环两个对象时才有效,效率不是很高,但对于小对象,我认为将对象串起来会更快——虽然不是 100% 肯定 @RamKumar 只需添加条件 var result = Object.keys(object1).every((key) =>(object1[key] === object2[key] && object1.hasOwnProperty(key ) && object2.hasOwnProperty(key))【参考方案16】:

这是 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;
  

demo available here

【讨论】:

如果对象键的顺序发生变化则不起作用。【参考方案17】:

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

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 ) return false 很明显,正确的相等检查器必须是递归的。我认为这样的递归答案之一应该是正确的答案。接受的答案没有给出代码,也没有帮助【参考方案18】:

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,其中键已排序,NOT 在其自身的对象中:

    ["a", ["b", 1]] ["b", 4]

【讨论】:

这是一个功能齐全的答案,感谢@Adriano Spadoni。你知道我怎样才能得到被修改的键/属性吗?谢谢, 嗨@digital,如果您需要不同的键,这不是理想的功能。检查另一个答案并使用一个循环遍历对象。 永远不要使用 JSON.stringify 来比较 json 对象。键的顺序预计不会相同。 嗨@Eksapsy,这就是为什么有一个“sort()”函数,你看到s(a) s(b)了吗?由于排序,此功能很好。该示例具有不同的其他键,目的是证明它有效。 @digital,要获得差异需要 RegExp 而不是“===”,这是可行的。【参考方案19】:

许多人没有意识到的解决这个问题的一个简单方法是对 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;
);

【讨论】:

哦,我也忘记了,但是如果它们是同一个对象,则可以通过首先测试对象相等和提早放弃来加速该功能: if (obj1 === obj2) return true; areEqual(a: 'b', b: 'a') 得到true 那么呢? 是的,我在发布后意识到这个“解决方案”有问题。它需要在排序算法中做更多的工作才能真正正常工作。【参考方案20】:

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

        
    )

【讨论】:

【参考方案21】:

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

【讨论】:

【参考方案22】:

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

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

【讨论】:

这不符合 OP 的要求,因为只有当两个对象都具有相同的键时才会匹配,而他们声明它们不会。它还要求键的顺序相同,这也不太合理。 什么是属性的顺序不同???不,不是一个好方法【参考方案23】:

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

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;

【讨论】:

我最终使用了这个的变体。感谢您计算成员的想法! 在修改 Object.prototype 时要非常小心——在绝大多数情况下,不建议这样做(例如,添加出现在所有 for..in 循环中)。也许考虑Object.equals = function(aObj, bObj) ...【参考方案24】:

如果您要比较 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:... 类似对象。

【讨论】:

【参考方案25】:

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

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

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

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

【讨论】:

【参考方案26】:

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

一些测试取自@EbrahimByagowi here 的回答。

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

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

【讨论】:

【参考方案27】:

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

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

在那种情况下,我将“相等”定义为表示 i​​d 值匹配。鉴于每个实例都有一个唯一的 id,这可以用来强化匹配对象也占用相同内存位置的想法。虽然这不是必需的。

【讨论】:

【参考方案28】:

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

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;

Source code(包括辅助函数、generalType 和 uniqueArray): Unit Test 和 Test Runner here。

【讨论】:

【参考方案29】:

我对这个函数做了以下假设:

    您可以控制要比较的对象,并且您只有原始值(即没有嵌套对象、函数等)。 您的浏览器支持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;

【讨论】:

【参考方案30】:

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

这比较: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;

【讨论】:

以上是关于如何确定两个 JavaScript 对象的相等性?的主要内容,如果未能解决你的问题,请参考以下文章

如何检查 Javascript 中 Unicode 字符串的相等性?

JavaScript 比较运算符:身份与平等

ECMAScript运算符之《等性运算符》

比较两个集合的相等性,而不考虑其中项目的顺序

《JavaScript高级程序设计(第三版)》-3

Linq 表达式如何确定相等性?