From 3ffefdf6b57a057ba65ff70ca05931118a1269de Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Sun, 16 Dec 2012 01:39:11 -0800 Subject: [PATCH] Avoid `_.isArray` returning `true` for `arguments` objects in browsers that report `arguments.constructor` as `Array`. Former-commit-id: 9fccc5219e7cb6a007138f1f474d9e68504a0260 --- build.js | 49 +++++++++++++++++++++++++++++++------------- build/pre-compile.js | 2 +- lodash.js | 21 +++++++++++-------- 3 files changed, 48 insertions(+), 24 deletions(-) diff --git a/build.js b/build.js index 95944f97b..4acaa309c 100755 --- a/build.js +++ b/build.js @@ -174,7 +174,7 @@ 'hasDontEnumBug', 'isKeysFast', 'objectLoop', - 'noArgsEnum', + 'nonEnumArgs', 'noCharByIndex', 'shadowed', 'top', @@ -903,6 +903,29 @@ return source; } + /** + * Removes all `argsAreObjects` references from `source`. + * + * @private + * @param {String} source The source to process. + * @returns {String} Returns the modified source. + */ + function removeArgsAreObjects(source) { + source = removeVar(source, 'argsAreObjects'); + + // remove `argsAreObjects` from `_.isArray` + source = source.replace(matchFunction(source, 'isArray'), function(match) { + return match.replace(/\(argsAreObjects && *([^)]+)\)/g, '$1'); + }); + + // remove `argsAreObjects` from `_.isEqual` + source = source.replace(matchFunction(source, 'isEqual'), function(match) { + return match.replace(/!argsAreObjects[^:]+:\s*/g, ''); + }); + + return source; + } + /** * Removes all `noArgsClass` references from `source`. * @@ -918,11 +941,6 @@ return match.replace(/ *\|\| *\(noArgsClass *&&[^)]+?\)\)/g, ''); }); - // remove `noArgsClass` from `_.isEqual` - source = source.replace(matchFunction(source, 'isEqual'), function(match) { - return match.replace(/noArgsClass[^:]+:\s*/g, ''); - }); - return source; } @@ -1628,9 +1646,11 @@ ' }' ].join('\n')); - // remove `arguments` object check from `_.isEqual` + // remove `arguments` object and `argsAreObjects` check from `_.isEqual` source = source.replace(matchFunction(source, 'isEqual'), function(match) { - return match.replace(/^ *if *\(.+== argsClass[^}]+}\n/gm, '') + return match + .replace(/^ *if *\(.+== argsClass[^}]+}\n/gm, '') + .replace(/!argsAreObjects[^:]+:\s*/g, ''); }); // remove conditional `charCodeCallback` use from `_.max` and `_.min` @@ -1816,8 +1836,9 @@ }); } else { - source = removeIsArgumentsFallback(source); + source = removeArgsAreObjects(source); source = removeHasObjectSpliceBug(source); + source = removeIsArgumentsFallback(source); } // remove `thisArg` from unexposed `forIn` and `forOwn` @@ -1834,10 +1855,10 @@ } }); - // remove `hasDontEnumBug`, `iteratesOwnLast`, and `noArgsEnum` declarations and assignments + // remove `hasDontEnumBug`, `iteratesOwnLast`, and `nonEnumArgs` declarations and assignments source = source .replace(/^ *\(function\(\) *{[\s\S]+?}\(1\)\);\n/m, '') - .replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var (?:hasDontEnumBug|iteratesOwnLast|noArgsEnum).+\n/g, ''); + .replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var (?:hasDontEnumBug|iteratesOwnLast|nonEnumArgs).+\n/g, ''); // remove `iteratesOwnLast` from `shimIsPlainObject` source = source.replace(matchFunction(source, 'shimIsPlainObject'), function(match) { @@ -2008,7 +2029,7 @@ } if ((source.match(/\bcreateIterator\b/g) || []).length < 2) { source = removeFunction(source, 'createIterator'); - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var noArgsEnum;|.+?noArgsEnum *=.+/g, ''); + source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var nonEnumArgs;|.+?nonEnumArgs *=.+/g, ''); } if (isRemoved(source, 'createIterator', 'bind', 'keys', 'template')) { source = removeVar(source, 'isBindFast'); @@ -2027,8 +2048,8 @@ source = removeVar(source, 'nativeKeys'); source = removeKeysOptimization(source); } - if (!source.match(/var (?:hasDontEnumBug|iteratesOwnLast|noArgsEnum)\b/g)) { - // remove `hasDontEnumBug`, `iteratesOwnLast`, and `noArgsEnum` assignments + if (!source.match(/var (?:hasDontEnumBug|iteratesOwnLast|nonEnumArgs)\b/g)) { + // remove `hasDontEnumBug`, `iteratesOwnLast`, and `nonEnumArgs` assignments source = source.replace(/ *\(function\(\) *{[\s\S]+?}\(1\)\);\n/, ''); } } diff --git a/build/pre-compile.js b/build/pre-compile.js index 67f96e276..6bcc7ea18 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -41,7 +41,7 @@ 'hasDontEnumBug', 'isKeysFast', 'objectLoop', - 'noArgsEnum', + 'nonEnumArgs', 'noCharByIndex', 'shadowed', 'top', diff --git a/lodash.js b/lodash.js index 69550026b..9d32fab23 100644 --- a/lodash.js +++ b/lodash.js @@ -150,20 +150,23 @@ arrayRef.splice.call(hasObjectSpliceBug, 0, 1), hasObjectSpliceBug[0]); /** Detect if an `arguments` object's indexes are non-enumerable (IE < 9) */ - var noArgsEnum = true; + var nonEnumArgs = true; (function() { var props = []; function ctor() { this.x = 1; } ctor.prototype = { 'valueOf': 1, 'y': 1 }; for (var prop in new ctor) { props.push(prop); } - for (prop in arguments) { noArgsEnum = !prop; } + for (prop in arguments) { nonEnumArgs = !prop; } hasDontEnumBug = !/valueOf/.test(props); iteratesOwnLast = props[0] != 'x'; }(1)); - /** Detect if an `arguments` object's [[Class]] is unresolvable (Firefox < 4, IE < 9) */ + /** Detect if `arguments` objects are `Object` objects (all but Opera < 10.5) */ + var argsAreObjects = arguments.constructor == Object; + + /** Detect if `arguments` objects [[Class]] is unresolvable (Firefox < 4, IE < 9) */ var noArgsClass = !isArguments(arguments); /** @@ -375,7 +378,7 @@ // object iteration: // add support for iterating over `arguments` objects if needed - ' <% } else if (noArgsEnum) { %>\n' + + ' <% } else if (nonEnumArgs) { %>\n' + ' var length = iteratee.length; index = -1;\n' + ' if (length && isArguments(iteratee)) {\n' + ' while (++index < length) {\n' + @@ -438,7 +441,7 @@ ' }' + ' <% } %>' + ' <% } %>' + - ' <% if (arrayLoop || noArgsEnum) { %>\n}<% } %>\n' + + ' <% if (arrayLoop || nonEnumArgs) { %>\n}<% } %>\n' + // add code to the bottom of the iteration function '<%= bottom %>;\n' + @@ -660,7 +663,7 @@ 'hasDontEnumBug': hasDontEnumBug, 'isKeysFast': isKeysFast, 'objectLoop': '', - 'noArgsEnum': noArgsEnum, + 'nonEnumArgs': nonEnumArgs, 'noCharByIndex': noCharByIndex, 'shadowed': shadowed, 'top': '', @@ -1202,7 +1205,7 @@ var isArray = nativeIsArray || function(value) { // `instanceof` may cause a memory leak in IE 7 if `value` is a host object // http://ajaxian.com/archives/working-aroung-the-instanceof-memory-leak - return value instanceof Array || toString.call(value) == arrayClass; + return (argsAreObjects && value instanceof Array) || toString.call(value) == arrayClass; }; /** @@ -1373,8 +1376,8 @@ return false; } // in older versions of Opera, `arguments` objects have `Array` constructors - var ctorA = noArgsClass && isArguments(a) ? Object : a.constructor, - ctorB = noArgsClass && isArguments(b) ? Object : b.constructor; + var ctorA = !argsAreObjects && isArguments(a) ? Object : a.constructor, + ctorB = !argsAreObjects && isArguments(b) ? Object : b.constructor; // non `Object` object instances with different constructors are not equal if (ctorA != ctorB && !(