From c7c57ca6ff0a7d175913e4c040450f5a15a9bdaa Mon Sep 17 00:00:00 2001 From: Michael Ficarra Date: Fri, 15 Jul 2011 18:15:10 -0400 Subject: [PATCH] _.isEqual improvements --- test/objects.js | 9 +++++++-- underscore.js | 22 ++++++++++------------ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/test/objects.js b/test/objects.js index 0b1685a88..4234f0fad 100644 --- a/test/objects.js +++ b/test/objects.js @@ -72,6 +72,8 @@ $(document).ready(function() { var moe = {name : 'moe', lucky : [13, 27, 34]}; var clone = {name : 'moe', lucky : [13, 27, 34]}; + var isEqualObj = {isEqual: function (o) { return o.isEqual == this.isEqual; }, unique: {}}; + var isEqualObjClone = {isEqual: isEqualObj.isEqual, unique: {}}; ok(moe != clone, 'basic equality between objects is false'); ok(_.isEqual(moe, clone), 'deep equality is true'); ok(_(moe).isEqual(clone), 'OO-style deep equality works'); @@ -86,8 +88,11 @@ $(document).ready(function() { ok(!_.isEqual({source: '(?:)', global: true, multiline: true, ignoreCase: true}, /(?:)/gim), 'RegExp-like objects and RegExps are not equal'); ok(!_.isEqual(null, [1]), 'a falsy is never equal to a truthy'); ok(!_.isEqual(undefined, null), '`undefined` is not equal to `null`'); - ok(_.isEqual({isEqual: function () { return true; }}, {}), 'first object implements `isEqual`'); - ok(_.isEqual({}, {isEqual: function () { return true; }}), 'second object implements `isEqual`'); + ok(_.isEqual(isEqualObj, isEqualObj), 'both objects implement `isEqual`, same objects'); + ok(_.isEqual(isEqualObj, isEqualObjClone), 'both objects implement `isEqual`, different objects'); + ok(_.isEqual(isEqualObjClone, isEqualObj), 'both objects implement `isEqual`, different objects, swapped'); + ok(!_.isEqual(isEqualObj, {}), 'first object implements `isEqual`'); + ok(!_.isEqual({}, isEqualObj), 'second object implements `isEqual`'); ok(!_.isEqual({x: 1, y: undefined}, {x: 1, z: 2}), 'objects with the same number of undefined keys are not equal'); ok(!_.isEqual(new Foo, new Bar), 'objects with different inherited properties are not equal'); ok(!_.isEqual(_({x: 1, y: undefined}).chain(), _({x: 1, z: 2}).chain()), 'wrapped objects are not equal'); diff --git a/underscore.js b/underscore.js index 9d95ebfbc..3ad38311b 100644 --- a/underscore.js +++ b/underscore.js @@ -606,29 +606,27 @@ // The type comparison above prevents unwanted type coercion. if (a == b) return true; // Optimization; ensure that both values are truthy or falsy. - if ((!a && b) || (a && !b)) return false; + if (!a != !b) return false; // `NaN` values are equal. if (_.isNaN(a)) return _.isNaN(b); // Compare dates by their millisecond values. - var isDateA = _.isDate(a), isDateB = _.isDate(b); - if (isDateA || isDateB) return isDateA && isDateB && a.getTime() == b.getTime(); + if (_.isDate(a)) return _.isDate(b) && a.getTime() == b.getTime(); // Compare RegExps by their source patterns and flags. - var isRegExpA = _.isRegExp(a), isRegExpB = _.isRegExp(b); - if (isRegExpA || isRegExpB) return isRegExpA && isRegExpB && - a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; + if (_.isRegExp(a)) + return _.isRegExp(b) && + a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; // Ensure that both values are objects. if (typeA != 'object') return false; // Unwrap any wrapped objects. if (a._chain) a = a._wrapped; if (b._chain) b = b._wrapped; // Invoke a custom `isEqual` method if one is provided. - if (a.isEqual) return a.isEqual(b); - if (b.isEqual) return b.isEqual(a); + if (typeof a.isEqual == 'function') return a.isEqual(b); // Compare array lengths to determine if a deep comparison is necessary. - if (a.length && (a.length !== b.length)) return false; + if ('length' in a && (a.length !== b.length)) return false; // Assume equality for cyclic structures. var length = stack.length; while (length--) {