为什么要重复造轮子呢?试试使用Lodash吧。它有许多必备的函数,如isEqual()。
_.isEqual(object, other);
它将暴力破解每个键值-就像此页面上的其他示例一样-使用ECMAScript 5和本地优化(如果浏览器支持)。
注意:以前这个答案推荐使用Underscore.js,但是lodash已经更好地修复了错误并解决了一致性问题。
简短回答
简单的答案是:不,没有通用的方法可以确定一个对象是否等于另一个对象,除非你严格地考虑一个对象是无类型的。
详细回答
这个概念是指比较两个不同对象实例以判断它们在值层面上是否相等的Equals方法。然而,具体类型要定义如何实现一个Equals方法。对具有原始值的属性进行迭代比较可能还不够:一个对象可能包含与相等无关的属性。例如,
function MyClass(a, b)
{
var c;
this.getCLazy = function() {
if (c === undefined) c = a * b // imagine * is really expensive
return c;
}
}
在上述情况中,决定任何两个MyClass实例是否相等的重要因素不是 c ,而是 a 和 b 。在某些情况下, c 可能会在实例之间发生变化,但在比较过程中并不重要。
请注意,当成员本身也是类型的实例时,此问题适用,并且每个实例都需要确定相等性。
进一步复杂化的是,在JavaScript中,数据和方法之间的区别变得模糊。
对象可以引用将作为事件处理程序调用的方法,并且这可能不被认为是其“值状态”的一部分。而另一个对象可能会被分配一个执行重要计算的函数,从而使该实例与其他对象不同,因为它引用了不同的函数。
那么,如果对象的现有原型方法被另一个函数覆盖,它仍然可以被视为与另一个完全相同的实例相等吗?对于每种类型,这个问题只能在每个具体情况下回答。
正如前面所述,唯一合理的选择是严格无类型对象的例外。在这种情况下,唯一明智的选择是迭代和递归比较每个成员。即使这样,人们也必须问一个函数的“值”是什么?_.isEqual(obj1, obj2);
即可。 - chovy.equals
方法并不是微不足道的,这就是为什么在《Effective Java》中专门有这样一个主题的原因。 - lcnvar 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
{x:1, y:2}
不等于{y:2, x:1}
。 - Stijn de Witt一个简短的功能性实现: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);
}
Object.keys(x).every(key => deepEqual(x[key], y[key]))
。 - jib: (x === y)
替换为 : (x === y && (x != null && y != null || x.constructor === y.constructor))
。 - atminfunction 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])
returns true
- Roy TinkerobjectEquals([1,2,3],{0:1,1:2,2:3})
也会返回 true
—— 例如,没有类型检查,只有键/值检查。 - Roy Tinkernew Map([[1,1]])
和 new Map()
会出现假阳性,而使用 {constructor: NaN}
则会出现假阴性。 - Monday Fatigue如果您正在使用 AngularJS,则可以使用 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>
如果您正在使用 JSON 库,可以将每个对象编码为 JSON,然后比较生成的字符串是否相等。
var obj1={test:"value"};
var obj2={test:"value2"};
alert(JSON.encode(obj1)===JSON.encode(obj2));
注意:虽然这个答案在许多情况下都可以工作,但正如一些人在评论中指出的那样,它因为各种原因存在问题。在几乎所有情况下,您都希望找到更稳健的解决方案。
如果您使用Node,可以使用本地的util
库中的一个方便的方法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
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
has this feature too. In its case, you'll use:
var foo = { a: 1 }; var bar = { a: 1 }; expect(foo).to.deep.equal(bar); // true;
- Folusho Oladipoerror.name
设置为 "AssertionError [ERR_ASSERTION]"
。在这种情况下,我会用 if (error.code === 'ERR_ASSERTION') {
替换 if 语句。 - Knute KnudsendeepStrictEqual
是正确的方法。我一直在绞尽脑汁地想弄清楚为什么 strictEqual
不起作用。太棒了。 - NetOperator Wibby您是否想要测试两个对象是否相等?例如:它们的属性是否相等?
如果是这种情况,您可能已经注意到了以下情况:
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()
函数可能是可行的方式。
a.hashCode() == b.hashCode()
并不意味着a
等于b
。这是一个必要条件,但不足以说明两者相等。 - Heinziperson = { name: "fred", age: 42 }
这样仅有很少属性的东西。如果你确实有这种情况并且必须通过完全相等来搜索它们,那么似乎是浪费了。大多数情况下,你的普通对象仍将具有许多属性 - 其中一个要么是唯一的,要么是你想要唯一的,例如某种ID。然后你可以通过该属性进行搜索,而不需要检查每个属性是否匹配。 - VLAZ