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

IT技术 javascript object equals hashcode
2020-12-21 00:25:00

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

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

6个回答

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

_.isEqual(object, other);

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

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

Underscore 的 isEqual 函数非常好(但你必须拉入他们的库才能使用它 - 大约 3K gzipped)。
2021-02-12 00:25:00
即使您负担不起将下划线作为依赖项,也可以将 isEqual 函数拉出,满足许可要求并继续。这是迄今为止在 stackoverflow 上提到的最全面的相等性测试。
2021-02-12 00:25:00
@mckoss 如果您不想要整个库npmjs.com/package/lodash.isequal,您可以使用独立module
2021-02-12 00:25:00
Underscore有一个分支叫做LoDash,该作者非常关心诸如此类的一致性问题。使用 LoDash 进行测试,看看你会得到什么。
2021-02-23 00:25:00
还要考虑deep-equal包,它甚至适用于对象数组。
2021-02-26 00:25:00

简短的回答

简单的答案是:不,没有通用的方法来确定一个对象在你的意义上等于另一个对象。例外是当您严格认为对象是无类型的。

长答案

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

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

一个对象的现有原型方法之一被另一个函数覆盖了怎么办?它是否仍然可以被视为等同于另一个相同的实例?该问题只能在每种类型的每种特定情况下得到回答。

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

如果你使用下划线,你可以做 _.isEqual(obj1, obj2);
2021-02-12 00:25:00
@Harsh,答案没有给出任何解决方案,因为没有。即使在 Java 中,对象相等性比较也没有灵丹妙药,正确实现该.equals方法并非易事,这就是Effective Java 中有这样一个主题的原因
2021-02-23 00:25:00
@Kumar Harsh,使两个对象相等的原因是特定于应用程序的;并非必须考虑对象的每个属性,因此强制执行对象的每个属性也不是具体的解决方案。
2021-02-24 00:25:00
javascript equality object谷歌搜索,得到了 tl;dr 的回复,从@chovy 的评论中取了一行。谢谢你
2021-03-05 00:25:00
什么是下划线?是图书馆吗?用于检查对象相等性的最小大小代码片段是多少?
2021-03-06 00:25:00

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

如果您在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>

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

if (x.constructor !== y.constructor) { return false; 在不同窗口中比较两个 'new String('a')' 时,这会中断。对于值相等,您必须检查两个对象上是否为 String.isString,然后使用松散的相等检查 'a == b'。
2021-02-10 00:25:00
objectEquals([1,2,3],{0:1,1:2,2:3})也返回true——例如没有类型检查,只有键/值检查。
2021-02-17 00:25:00
“value”平等和“严格”平等之间存在巨大差异,它们不应该以相同的方式实现。值相等不应该关心类型,除了基本结构,它是以下 4 种之一:“对象”(即键/值对的集合)、“数字”、“字符串”或“数组”。就是这样。任何不是数字、字符串或数组的东西都应该作为一组键/值对进行比较,而不管构造函数是什么(跨窗口安全)。比较对象时,将文字数字的值和 Number 的实例等同起来,但不要将字符串强制转换为数字。
2021-02-18 00:25:00
objectEquals([1,2,undefined],[1,2]) 回报 true
2021-03-08 00:25:00
objectEquals(new Date(1234),1234) 回报 true
2021-03-09 00:25:00