如何确定两个JavaScript对象的相等性?
一个严格的相等运算符会告诉你两个对象types是否相等。 然而,有没有办法判断两个对象是否相等, 就像 Java中的哈希码值?
Stack Overflow问题JavaScript中是否有任何一种hashCode函数? 是类似于这个问题,但需要更多的学术答案。 上面的场景说明了为什么有必要有一个,我想知道是否有任何等效的解决scheme 。
简短的回答
简单的答案是:不,没有一个通用的方法来确定一个物体是否与另一个物体相同。 当你严格考虑一个没有types的对象的时候是个例外。
长的答案
这个概念是Equals方法的一个概念,该方法比较对象的两个不同实例,以指示它们在值级别是否相等。 然而,定义如何实施Equals
方法取决于具体的types。 具有原始值的属性的迭代比较可能是不够的,可能有属性不被视为对象值的一部分。 例如,
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
可能在实例之间有所不同,但在比较过程中却不是很重要
注意这个问题适用于成员本身也可以是一个types的实例,并且每个成员都需要有一个确定平等的方法。
更复杂的是,在JavaScript中,数据和方法之间的区别是模糊的。
一个对象可能会引用一个被称为事件处理程序的方法,这可能不会被视为其“值状态”的一部分。 而另一个对象可能被赋予一个执行重要计算的function,从而使这个实例与其他实例不同,因为它引用了不同的function。
怎么样一个对象有一个现有的原型方法被另一个函数覆盖? 它仍然被认为是相等的另一个实例,否则它是相同的? 这个问题只能在每种types的具体情况下得到回答。
如前所述,这个例外将是一个严格无types的对象。 在这种情况下,唯一明智的select是每个成员的迭代和recursion比较。 即使这样,人们也不得不问一个函数的“价值”是什么?
为什么重新发明轮子? 给Lodash一个尝试。 它有许多必须的函数,如isEqual() 。
_.isEqual(object, other);
它会蛮力检查每个键值 – 就像本页面上的其他示例 – 使用ECMAScript 5和本地优化(如果它们在浏览器中可用)。
注意:以前这个答案build议Underscore.js ,但是lodash在修正错误和解决一致性问题方面做得更好。
当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
如果您在AngularJS中工作,那么angular.equals
函数将确定两个对象是否相等。 在Ember.js中使用isEqual
。
-
angular.equals
– 有关此方法的更多信息,请参阅文档或源代码 。 它也对arrays做了深入的比较。 - 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,然后比较得到的string是否相等。
var obj1={test:"value"}; var obj2={test:"value2"}; alert(JSON.encode(obj1)===JSON.encode(obj2));
注意:虽然这个答案在很多情况下都能正常工作,但正如几个人在评论中指出的那样,由于种种原因,这是有问题的。 在几乎所有的情况下,你会想find一个更强大的解决scheme。
这是我的版本。 它使用ES5中引入的新Object.keysfunction以及来自+ , +和+的想法/testing:
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 } }));
如果您有方便的深层复制function,您可以使用下面的技巧, 仍然使用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
已经存在的属性将被简单地覆盖,它们在克隆中的顺序将被保留。
你想testing两个对象是否相等? 即:他们的属性是平等的?
如果是这样的话,你可能会注意到这种情况:
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指出的那样,你可能需要根据自己的目的来调整它,例如:不同的类可能有不同的“equal”定义。 如果你只是使用普通的对象,上述可能就足够了,否则自定义的MyClass.equals()
函数可能是要走的路。
在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; };
短functiondeepEqual
实现:
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,使用臂架的build议和ES6箭头function:
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); }
Heres是ES6 / ES2015中使用function风格方法的解决scheme:
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; } }
演示可在这里
这个问题的一个简单的解决scheme,很多人没有意识到是sortingJSONstring(每个字符)。 这通常也比这里提到的其他解决scheme更快:
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函数传递一个“replacer”函数来过滤比较( 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; });
我使用这个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>
如果通过Babel或其他方式使用ES6 + ,则还可以使用Object.is(x, y)
。
参考: http : //wiki.ecmascript.org/doku.php?id= harmony: egal#object.is_x_y
最简单和合乎逻辑的解决scheme,用于比较所有对象,数组,string,内容…
JSON.stringify({a: val1}) === JSON.stringify({a: val2})
注意:你需要用你的对象replaceval1
和val2
我build议不要哈希或序列化(如JSON解决schemebuild议)。 如果你需要testing两个对象是否相等,那么你需要定义等于什么意思。 这可能是两个对象中的所有数据成员匹配,也可能是内存位置必须匹配(意味着两个variables都引用内存中的同一个对象),也可能是每个对象中只有一个数据成员必须匹配。
最近我开发了一个对象,其构造函数每次创build一个实例时都会创build一个新的ID(从1开始,递增1)。 此对象有一个isEqual函数,用于将该id值与另一个对象的id值进行比较,如果匹配,则返回true。
在这种情况下,我将“平等”定义为id值匹配。 鉴于每个实例都有一个唯一的ID,这可以用来强制匹配的对象也占用相同的内存位置的想法。 虽然这不是必要的。
需要一个更通用的对象比较函数比已张贴,我煮熟以下。 批评赞赏…
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; }
如果您正在比较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:{...}}
对象。
我面临同样的问题,决定写我自己的解决scheme。 但是因为我也想将数组与对象进行比较,反之亦然,我制定了一个通用的解决scheme。 我决定添加function到原型,但可以轻松地将它们重写为独立的function。 这里是代码:
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; }
该algorithm分为两部分, equals函数本身和一个函数,用于查找数组/对象中某个属性的数字索引。 查找function只需要,因为indexof只能find数字和string,没有对象。
人们可以这样称呼它:
({a: 1, b: "h"}).equals({a: 1, b: "h"});
该函数返回true或false,在这种情况下是true。 algorithmals允许比较非常复杂的对象:
({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,即使属性有不同的顺序。 一个小的细节要注意:这个代码也检查相同types的两个variables,所以“3”与3不一样。
我不知道是否有人发布了类似的东西,但是这里有一个函数来检查对象的平等性。
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; }
此外,它是recursion的,所以它也可以检查深度平等,如果这就是你所说的。
我正在用这个函数做出以下假设:
- 你控制你正在比较的对象,你只有原始值(即没有嵌套的对象,function等)。
- 您的浏览器支持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; }
这是所有上述的补充,而不是替代品。 如果您需要快速浅比较对象而无需检查额外的recursion情况。 这是一个镜头。
这比较为:1)自己属性的数量相等,2)键名相等,3)如果bCompareValues == true,相应属性值及其types的相等性(三重相等)
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; }
为了比较简单的键/值对对象实例的键,我使用:
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个属性…
我知道这有点旧,但我想添加一个解决scheme,我想出了这个问题。 我有一个对象,我想知道它的数据何时改变。 “类似于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) }
这里可以复制并创build另一组数组来比较值和键。 这是非常简单的,因为他们现在是数组,并且如果对象具有不同的大小将返回false。
从我个人的图书馆中拉出来,这是我反复使用我的作品。 下面的函数是一个宽松的recursion平等,它不检查
- 阶级平等
- inheritance的值
- 价值严格平等
我主要用这个来检查我是否对各种API实现得到同等的答复。 可能会出现实现差异(如string与数字)和额外的空值。
它的实现非常简单和简短(如果所有的评论都被删除)
/** Recursively check if both objects are equal in value *** *** This function is designed to use multiple methods from most probable *** (and in most cases) valid, to the more regid and complex method. *** *** One of the main principles behind the various check is that while *** some of the simpler checks such as == or JSON may cause false negatives, *** they do not cause false positives. As such they can be safely run first. *** *** # !Important Note: *** as this function is designed for simplified deep equal checks it is not designed *** for the following *** *** - Class equality, (ClassA().a = 1) maybe valid to (ClassB().b = 1) *** - Inherited values, this actually ignores them *** - Values being strictly equal, "1" is equal to 1 (see the basic equality check on this) *** - Performance across all cases. This is designed for high performance on the *** most probable cases of == / JSON equality. Consider bench testing, if you have *** more 'complex' requirments *** *** @param objA : First object to compare *** @param objB : 2nd object to compare *** @param .... : Any other objects to compare *** *** @returns true if all equals, or false if invalid *** *** @license Copyright by eugene@picoded.com, 2012. *** Licensed under the MIT license: http://opensource.org/licenses/MIT **/ function simpleRecusiveDeepEqual(objA, objB) { // Multiple comparision check //-------------------------------------------- var args = Array.prototype.slice.call(arguments); if(args.length > 2) { for(var a=1; a<args.length; ++a) { if(!simpleRecusiveDeepEqual(args[a-1], args[a])) { return false; } } return true; } else if(args.length < 2) { throw "simpleRecusiveDeepEqual, requires atleast 2 arguments"; } // basic equality check, //-------------------------------------------- // if this succed the 2 basic values is equal, // such as numbers and string. // // or its actually the same object pointer. Bam // // Note that if string and number strictly equal is required // change the equality from ==, to === // if(objA == objB) { return true; } // If a value is a bsic type, and failed above. This fails var basicTypes = ["boolean", "number", "string"]; if( basicTypes.indexOf(typeof objA) >= 0 || basicTypes.indexOf(typeof objB) >= 0 ) { return false; } // JSON equality check, //-------------------------------------------- // this can fail, if the JSON stringify the objects in the wrong order // for example the following may fail, due to different string order: // // JSON.stringify( {a:1, b:2} ) == JSON.stringify( {b:2, a:1} ) // if(JSON.stringify(objA) == JSON.stringify(objB)) { return true; } // Array equality check //-------------------------------------------- // This is performed prior to iteration check, // Without this check the following would have been considered valid // // simpleRecusiveDeepEqual( { 0:1963 }, [1963] ); // // Note that u may remove this segment if this is what is intended // if( Array.isArray(objA) ) { //objA is array, objB is not an array if( !Array.isArray(objB) ) { return false; } } else if( Array.isArray(objB) ) { //objA is not array, objB is an array return false; } // Nested values iteration //-------------------------------------------- // Scan and iterate all the nested values, and check for non equal values recusively // // Note that this does not check against null equality, remove the various "!= null" // if this is required var i; //reuse var to iterate // Check objA values against objB for (i in objA) { //Protect against inherited properties if(objA.hasOwnProperty(i)) { if(objB.hasOwnProperty(i)) { // Check if deep equal is valid if(!simpleRecusiveDeepEqual( objA[i], objB[i] )) { return false; } } else if(objA[i] != null) { //ignore null values in objA, that objB does not have //else fails return false; } } } // Check if objB has additional values, that objA do not, fail if so for (i in objB) { if(objB.hasOwnProperty(i)) { if(objB[i] != null && !objA.hasOwnProperty(i)) { //ignore null values in objB, that objA does not have //else fails return false; } } } // End of all checks //-------------------------------------------- // By reaching here, all iteration scans have been done. // and should have returned false if it failed return true; } // Sanity checking of simpleRecusiveDeepEqual (function() { if( // Basic checks !simpleRecusiveDeepEqual({}, {}) || !simpleRecusiveDeepEqual([], []) || !simpleRecusiveDeepEqual(['a'], ['a']) || // Not strict checks !simpleRecusiveDeepEqual("1", 1) || // Multiple objects check !simpleRecusiveDeepEqual( { a:[1,2] }, { a:[1,2] }, { a:[1,2] } ) || // Ensure distinction between array and object (the following should fail) simpleRecusiveDeepEqual( [1963], { 0:1963 } ) || // Null strict checks simpleRecusiveDeepEqual( 0, null ) || simpleRecusiveDeepEqual( "", null ) || // Last "false" exists to make the various check above easy to comment in/out false ) { alert("FATAL ERROR: simpleRecusiveDeepEqual failed basic checks"); } else { //added this last line, for SO snippet alert on success alert("simpleRecusiveDeepEqual: Passed all checks, Yays!"); } })();
Just wanted to contribute my version of objects comparison utilizing some es6 features. It doesn't take an order into account. After converting all if/else's to ternary I've came with following:
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; } ) }
Depends on what you mean by equality. And therefore it is up to you, as the developer of the classes, to define their equality.
There's one case used sometimes, where two instances are considered 'equal' if they point to the same location in memory, but that is not always what you want. For instance, if I have a Person class, I might want to consider two Person objects 'equal' if they have the same Last Name, First Name, and Social Security Number (even if they point to different locations in memory).
On the other hand, we can't simply say that two objects are equal if the value of each of their members is the same, since, sometimes, you don't want that. In other words, for each class, it's up to the class developer to define what members make up the objects 'identity' and develop a proper equality operator (be it via overloading the == operator or an Equals method).
Saying that two objects are equal if they have the same hash is one way out. However you then have to wonder how the hash is calculated for each instance. Going back to the Person example above, we could use this system if the hash was calculated by looking at the values of the First Name, Last Name, and Social Security Number fields. On top of that, we are then relying on the quality of the hashing method (that's a huge topic on its own, but suffice it to say that not all hashes are created equal, and bad hashing methods can lead to more collisions, which in this case would return false matches).
It's useful to consider two objects equal if they have all the same values for all properties and recursively for all nested objects and arrays. I also consider the following two objects equal:
var a = {p1: 1}; var b = {p1: 1, p2: undefined};
Similarly, arrays can have "missing" elements and undefined elements. I would treat those the same as well:
var c = [1, 2]; var d = [1, 2, undefined];
A function that implements this definition of equality:
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 (including the helper functions, generalType and uniqueArray): Unit Test and Test Runner here .
Here is a very basic approach to checking an object's "value equality".
var john = { occupation: "Web Developer", age: 25 }; var bobby = { occupation: "Web Developer", age: 25 }; function isEquivalent(a, b) { // Create arrays of property names var aProps = Object.getOwnPropertyNames(a); var bProps = Object.getOwnPropertyNames(b); // If number of properties is different, objects are not equivalent if (aProps.length != bProps.length) { return false; } for (var i = 0; i < aProps.length; i++) { var propName = aProps[i]; // If values of same property are not equal, objects are not equivalent if (a[propName] !== b[propName]) { return false; } } // If we made it this far, objects are considered equivalent return true; } // Outputs: true console.log(isEquivalent(john, bobby));
Demo – JSFiddle
As you can see, to check the objects' "value equality" we essentially have to iterate over every property in the objects to see whether they are equal. And while this simple implementation works for our example, there are a lot of cases that it doesn't handle. 例如:
- What if one of the property values is itself an object?
- What if one of the property values is NaN (the only value in JavaScript that is not equal to itself?)
- What if a has a property with value undefined, while b doesn't have this property (which thus evaluates to undefined?)
For a robust method of checking objects' "value equality" it is better to rely on a well-tested library that covers the various edge cases like Underscore .
var john = { occupation: "Web Developer", age: 25 }; var bobby = { occupation: "Web Developer", age: 25 }; // Outputs: true console.log(_.isEqual(john, bobby));
Demo – JSFiddle
I need to mock jQuery POST requests, so the equality that matters to me is that both objects have the same set of properties (none missing in either object), and that each property value is "equal" (according to this definition). I don't care about the objects having mismatching methods.
Here's what I'll be using, it should be good enough for my specific requirements:
function PostRequest() { for (var i = 0; i < arguments.length; i += 2) { this[arguments[i]] = arguments[i+1]; } var compare = function(u, v) { if (typeof(u) != typeof(v)) { return false; } var allkeys = {}; for (var i in u) { allkeys[i] = 1; } for (var i in v) { allkeys[i] = 1; } for (var i in allkeys) { if (u.hasOwnProperty(i) != v.hasOwnProperty(i)) { if ((u.hasOwnProperty(i) && typeof(u[i]) == 'function') || (v.hasOwnProperty(i) && typeof(v[i]) == 'function')) { continue; } else { return false; } } if (typeof(u[i]) != typeof(v[i])) { return false; } if (typeof(u[i]) == 'object') { if (!compare(u[i], v[i])) { return false; } } else { if (u[i] !== v[i]) { return false; } } } return true; }; this.equals = function(o) { return compare(this, o); }; return this; }
Use like so:
foo = new PostRequest('text', 'hello', 'html', '<p>hello</p>'); foo.equals({ html: '<p>hello</p>', text: 'hello' });