From 8572445f5723c803bb96ceba473968c8fb28159c Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Sun, 8 Jun 2014 13:14:23 -0700 Subject: [PATCH] Optimize `_.isEqual` by using `_.keys` and ensure `arguments` objects are compared correctly. --- lodash.js | 114 +++++++++++++++++++++++++-------------------------- test/test.js | 6 ++- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/lodash.js b/lodash.js index 8a9989107..a144a6dab 100644 --- a/lodash.js +++ b/lodash.js @@ -1278,13 +1278,16 @@ return stackB[length]; } } - result = isArr ? Ctor(value.length) : new Ctor(); + result = isArr ? Ctor(value.length) : new Ctor; } else { result = isArr ? slice(value) : baseAssign({}, value); } + if (className == argsClass || (!support.argsClass && isArguments(value))) { + result.length = value.length; + } // add array properties assigned by `RegExp#exec` - if (isArr) { + else if (isArr) { if (hasOwnProperty.call(value, 'index')) { result.index = value.index; } @@ -1774,8 +1777,8 @@ return false; } var valClass = toString.call(value), - othClass = toString.call(other), valIsArg = valClass == argsClass, + othClass = toString.call(other), othIsArg = othClass == argsClass; if (valIsArg) { @@ -1809,6 +1812,10 @@ // treat string primitives and their corresponding object instances as equal return value == String(other); } + if (!support.argsObject) { + valIsArg = isArguments(value); + othIsArg = isArguments(other); + } var isArr = arrayLikeClasses[valClass]; if (!isArr) { // exit for functions and DOM nodes @@ -1822,10 +1829,6 @@ if (valWrapped || othWrapped) { return baseIsEqual(valWrapped ? value.__wrapped__ : value, othWrapped ? other.__wrapped__ : other, callback, isWhere, stackA, stackB); } - if (!support.argsObject) { - valIsArg = isArguments(value); - othIsArg = isArguments(other); - } var hasValCtor = !valIsArg && hasOwnProperty.call(value, 'constructor'), hasOthCtor = !othIsArg && hasOwnProperty.call(other, 'constructor'); @@ -1858,7 +1861,7 @@ return stackB[length] == other; } } - result = true; + var index = -1; // add `value` and `other` to the stack of traversed objects stackA.push(value); @@ -1866,32 +1869,60 @@ // recursively compare objects and arrays (susceptible to call stack limits) if (isArr) { - // compare lengths to determine if a deep comparison is necessary var othLength = other.length; length = value.length; - result = othLength == length; + result = length == othLength; if (result || isWhere) { - var othIndex = -1; - // deep compare the contents, ignoring non-numeric properties - while (++othIndex < othLength) { - var othValue = other[othIndex]; - + while (++index < length) { + var valValue = value[index]; if (isWhere) { - var index = -1; - while (++index < length) { - result = baseIsEqual(value[index], othValue, callback, isWhere, stackA, stackB); + var othIndex = -1; + while (++othIndex < othLength) { + var othValue = other[othIndex]; + result = baseIsEqual(valValue, othValue, callback, isWhere, stackA, stackB); if (result) { break; } } } else { - var valValue = value[othIndex]; - result = callback ? callback(valValue, othValue, othIndex) : undefined; - result = typeof result == 'undefined' - ? baseIsEqual(valValue, othValue, callback, isWhere, stackA, stackB) - : !!result; + othValue = other[index]; + result = callback ? callback(valValue, othValue, index) : undefined; + if (typeof result == 'undefined') { + result = baseIsEqual(valValue, othValue, callback, isWhere, stackA, stackB); + } + if (!result) { + break; + } + } + } + } + } + else { + var valProps = keys(value), + othProps = keys(other); + + if (valIsArg) { + valProps.push('length'); + } + if (othIsArg) { + othProps.push('length'); + } + length = valProps.length; + result = length == othProps.length; + + if (result || isWhere) { + while (++index < length) { + var key = valProps[index]; + result = hasOwnProperty.call(other, key); + if (result) { + othValue = other[key]; + valValue = value[key]; + result = callback ? callback(valValue, othValue, key) : undefined; + if (typeof result == 'undefined') { + result = baseIsEqual(valValue, othValue, callback, isWhere, stackA, stackB); + } } if (!result) { break; @@ -1899,43 +1930,10 @@ } } } - else { - var size = 0; - - // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys` - // which, in this case, is more costly - baseForIn(other, function(othValue, key, other) { - if (hasOwnProperty.call(other, key)) { - result = false; - // count the number of properties - size++; - // deep compare each property value - if (hasOwnProperty.call(value, key)) { - var valValue = value[key]; - result = callback ? callback(valValue, othValue, key) : undefined; - result = typeof result == 'undefined' - ? baseIsEqual(valValue, othValue, callback, isWhere, stackA, stackB) - : !!result; - } - return result; - } - }); - - if (result && !isWhere) { - // ensure both objects have the same number of properties - baseForIn(value, function(valValue, key, value) { - if (hasOwnProperty.call(value, key)) { - // `size` will be `-1` if `value` has more properties than `other` - result = --size > -1; - return result; - } - }); - } - } stackA.pop(); stackB.pop(); - return result; + return !!result; } /** @@ -8198,7 +8196,7 @@ while (length--) { var key = props[length]; if (!(hasOwnProperty.call(object, key) && - baseIsEqual(object[key], source[key], null, true))) { + baseIsEqual(source[key], object[key], null, true))) { return false; } } diff --git a/test/test.js b/test/test.js index 4716c809a..3a82b949f 100644 --- a/test/test.js +++ b/test/test.js @@ -4661,7 +4661,7 @@ } }); - test('should treat `arguments` objects like `Object` objects', 2, function() { + test('should treat `arguments` objects like `Object` objects', 4, function() { var args = (function() { return arguments; }(1, 2, 3)), object = { '0': 1, '1': 2, '2': 3, 'length': 3 }; @@ -4669,12 +4669,14 @@ Foo.prototype = object; strictEqual(_.isEqual(args, object), true); + strictEqual(_.isEqual(object, args), true); if (!isPhantom) { strictEqual(_.isEqual(args, new Foo), false); + strictEqual(_.isEqual(new Foo, args), false); } else { - skipTest(); + skipTest(2); } });