严格的相等运算符将告诉您两个对象类型是否相等.但是,有没有办法判断两个对象是否相等,就像 Java中的哈希码值一样?
Stack Overflow问题JavaScript中是否有任何类型的hashCode函数?类似于这个问题,但需要更多的学术答案.上面的场景说明了为什么需要有一个,我想知道是否有任何等效的解决方案.
为什么重新发明轮子?给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; } }
在上面的例子中,c
确定MyClass的任何两个实例是否相等是唯一a
且b
重要的并不重要.在某些情况下c
,实例之间可能会有所不同,但在比较期间却不显着.
请注意,当成员本身也可以是类型的实例时,此问题适用,并且这些实例都需要具有确定相等性的方法.
更复杂的是,在JavaScript中,数据和方法之间的区别是模糊的.
一个对象可以引用一个被称为事件处理程序的方法,这可能不会被视为其"值状态"的一部分.而另一个对象可能被赋予执行重要计算的功能,从而使该实例与其他对象不同,仅仅因为它引用了不同的功能.
如果某个对象的某个现有原型方法被另一个函数覆盖了?是否仍然可以认为它与其他实例相同?这个问题只能在每种类型的每个特定情况下得到解答.
如前所述,异常将是严格无类型的对象.在这种情况下,唯一明智的选择是每个成员的迭代和递归比较.即使这样,人们也要问一个函数的"价值"是什么?
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
如果您在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');
}
这是我的版本.它正在使用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('Passed'); }
else { document.write('Failed'); }
};
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 } }));
如果您使用的是JSON库,则可以将每个对象编码为JSON,然后将生成的字符串进行相等性比较.
var obj1={test:"value"}; var obj2={test:"value2"}; alert(JSON.encode(obj1)===JSON.encode(obj2));
注意:虽然这个答案在许多情况下都有效,但正如几位人士在评论中所指出的那样,出于各种原因这是有问题的.在几乎所有情况下,您都希望找到更强大的解决方案.
功能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); }
如果你有一个深拷贝功能方便,你可以使用下面的技巧,仍然使用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
将被覆盖,因此将保留克隆中的顺序.
你是否试图测试两个物体是否相等?即:他们的财产是平等的?
如果是这种情况,您可能已经注意到这种情况:
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()
函数可能就好了.
最简单和逻辑的解决方案,用于比较像Object,Array,String,Int ...
JSON.stringify({a: val1}) === JSON.stringify({a: val2})
注意:
你需要替换val1
和val2
你的对象
对于对象,您必须递归(按键)对两个侧对象进行排序
在Node.js中,您可以使用其本机require("assert").deepEqual
.更多信息:http:
//nodejs.org/api/assert.html
例如:
var assert = require("assert"); assert.deepEqual({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; };
我使用这个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)));
Heres是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; } }
演示在这里
我不知道是否有人发布了与此类似的内容,但这里是我检查对象等式的函数.
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; }
此外,它是递归的,所以它也可以检查深度相等,如果这就是你所说的.
你可以使用_.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
假定对象中属性的顺序未更改。
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));
对于那些使用NodeJS的用户,可以isDeepStrictEqual
在本机Util库上调用一种方便的方法来实现此目的。
const util = require('util'); const foo = { hey: "ho", lets: "go" } const bar = { hey: "ho", lets: "go" } foo == bar // false util.isDeepStrictEqual(foo, bar) // true
https://nodejs.org/api/util.html#util_util_isdeepstrictequal_val1_val2