diff --git a/test/objects.js b/test/objects.js index 08206b913..0b1685a88 100644 --- a/test/objects.js +++ b/test/objects.js @@ -79,9 +79,13 @@ $(document).ready(function() { ok(NaN != NaN, 'NaN is not equal to NaN (native equality)'); ok(NaN !== NaN, 'NaN is not equal to NaN (native identity)'); ok(_.isEqual(NaN, NaN), 'NaN is equal to NaN'); + ok(!_.isEqual(5, NaN), '`5` is not equal to `NaN`'); + ok(!_.isEqual(false, NaN), '`false` is not equal to `NaN`'); ok(_.isEqual(new Date(100), new Date(100)), 'identical dates are equal'); ok(_.isEqual((/hello/ig), (/hello/ig)), 'identical regexes are equal'); + 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({x: 1, y: undefined}, {x: 1, z: 2}), 'objects with the same number of undefined keys are not equal'); diff --git a/underscore.js b/underscore.js index 14d5c01ed..9d95ebfbc 100644 --- a/underscore.js +++ b/underscore.js @@ -595,7 +595,8 @@ // Internal recursive comparison function. function eq(a, b, stack) { - // Identical objects are equal. + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. if (a === b) return a !== 0 || 1 / a == 1 / b; // A strict comparison is necessary because `null == undefined`. if (a == null) return a === b; @@ -604,17 +605,21 @@ if (typeA != typeof b) return false; // The type comparison above prevents unwanted type coercion. if (a == b) return true; - // Ensure that both values are truthy or falsy. + // Optimization; ensure that both values are truthy or falsy. if ((!a && b) || (a && !b)) return false; // `NaN` values are equal. if (_.isNaN(a)) return _.isNaN(b); - if (_.isDate(a)) return _.isDate(b) && a.getTime() == b.getTime(); + // Compare dates by their millisecond values. + var isDateA = _.isDate(a), isDateB = _.isDate(b); + if (isDateA || isDateB) return isDateA && isDateB && a.getTime() == b.getTime(); // Compare RegExps by their source patterns and flags. - if (_.isRegExp(a)) return _.isRegExp(b) && a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; - // Recursively compare objects and arrays. + 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; + // Ensure that both values are objects. if (typeA != 'object') return false; // Unwrap any wrapped objects. if (a._chain) a = a._wrapped; @@ -629,9 +634,9 @@ while (length--) { if (stack[length] == a) return true; } - // Add the object to the stack of traversed objects. + // Add the first object to the stack of traversed objects. stack.push(a); - // Deep compare the contents. + // Deep compare the two objects. var size = 0, sizeRight = 0, result = true, key; for (key in a) { // Count the expected number of properties. @@ -646,7 +651,7 @@ } result = size == sizeRight; } - // Remove the object from the stack of traversed objects. + // Remove the first object from the stack of traversed objects. stack.pop(); return result; }