From 7b918f77a99fdac982678ea43e6cec57b598c60a Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Mon, 4 Feb 2013 23:55:52 -0800 Subject: [PATCH] Clarify browsers affected by `nonEnumArgs` and reduce code around `hasEnumPrototype`. Former-commit-id: 587f755332accbca26dc1eb357a66d4f898aad88 --- build.js | 48 ++++++++++++++++++++++++-------------------- build/pre-compile.js | 1 - lodash.js | 29 ++++++++++++++++---------- 3 files changed, 44 insertions(+), 34 deletions(-) diff --git a/build.js b/build.js index 928608440..9c4faf530 100755 --- a/build.js +++ b/build.js @@ -805,29 +805,24 @@ // match multi-line comment block (could be on a single line) '(?:\\n +/\\*[^*]*\\*+(?:[^/][^*]*\\*+)*/\\n)?' + // begin non-capturing group - '(?:' + + '( *)(?:' + // match a function declaration - '( *)function ' + funcName + '\\b[\\s\\S]+?\\n\\1}|' + - // match a variable declaration with `createIterator` - ' +var ' + funcName + ' *=.*?createIterator\\((?:{|[a-zA-Z])[\\s\\S]+?\\);|' + + 'function ' + funcName + '\\b[\\s\\S]+?\\n\\1}|' + // match a variable declaration with function expression - '( *)var ' + funcName + ' *=.*?function[\\s\\S]+?\\n\\2};' + + 'var ' + funcName + ' *=.*?function[\\s\\S]+?\\n\\1};' + // end non-capturing group ')\\n' )); - if (result) { - return result[0]; - } // match variables that are explicitly defined as functions - result = source.match(RegExp( + result || (result = source.match(RegExp( // match multi-line comment block '(?:\\n +/\\*[^*]*\\*+(?:[^/][^*]*\\*+)*/)?\\n' + - // match a simple variable declaration - ' *var ' + funcName + ' *=.+?;\\n' - )); + // match simple variable declarations and those with `createIterator` + ' *var ' + funcName + ' *=(?:.+?|.*?createIterator\\([\\s\\S]+?\\));\\n' + ))); - return /@type +Function/.test(result) ? result[0] : ''; + return /@type +Function|function\s*\w*\(/.test(result) ? result[0] : ''; } /** @@ -953,12 +948,12 @@ source = removeFromCreateIterator(source, 'shadowed'); // remove `hasDontEnumBug` declaration and assignment - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var hasDontEnumBug;|.+?hasDontEnumBug *=.+/g, ''); + source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var hasDontEnumBug\b.*|.+?hasDontEnumBug *=.+/g, ''); // remove `shadowed` variable source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var shadowed[\s\S]+?;\n/, ''); - // remove JScript `[[DontEnum]]` fix from `iteratorTemplate` + // remove `hasDontEnumBug` from `iteratorTemplate` source = source.replace(getIteratorTemplate(source), function(match) { return match.replace(/(?: *\/\/.*\n)* *["']( *)<% *if *\(hasDontEnumBug[\s\S]+?["']\1<% *} *%>.+/, ''); }); @@ -977,14 +972,16 @@ source = removeFromCreateIterator(source, 'hasEnumPrototype'); // remove `hasEnumPrototype` declaration and assignment - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var hasEnumPrototype;|.+?hasEnumPrototype *=.+/g, ''); + source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var hasEnumPrototype\b.*|.+?hasEnumPrototype *=.+/g, ''); - // remove `prototype` [[Enumerable]] fix from `_.keys` + // remove `hasEnumPrototype` from `_.keys` source = source.replace(matchFunction(source, 'keys'), function(match) { - return match.replace(/(?:\s*\/\/.*)*(\s*return *).+?propertyIsEnumerable[\s\S]+?: */, '$1'); + return match + .replace(/\(hasEnumPrototype[^)]+\)(?:\s*\|\|\s*)?/, '') + .replace(/\s*if *\(\s*\)[^}]+}/, ''); }); - // remove `prototype` [[Enumerable]] fix from `iteratorTemplate` + // remove `hasEnumPrototype` from `iteratorTemplate` source = source.replace(getIteratorTemplate(source), function(match) { return match .replace(/(?: *\/\/.*\n)* *["'] *(?:<% *)?if *\(hasEnumPrototype *(?:&&|\))[\s\S]+?<% *} *(?:%>|["']).+/g, '') @@ -1025,7 +1022,7 @@ */ function removeIteratesOwnLast(source) { // remove `iteratesOwnLast` declaration and assignment - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var iteratesOwnLast;|.+?iteratesOwnLast *=.+/g, ''); + source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var iteratesOwnLast\b.*|.+?iteratesOwnLast *=.+/g, ''); // remove `iteratesOwnLast` from `shimIsPlainObject` source = source.replace(matchFunction(source, 'shimIsPlainObject'), function(match) { @@ -1117,9 +1114,16 @@ source = removeFromCreateIterator(source, 'nonEnumArgs'); // remove `nonEnumArgs` declaration and assignment - source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var nonEnumArgs;|.+?nonEnumArgs *=.+/g, ''); + source = source.replace(/(?:\n +\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\/)?\n *var nonEnumArgs\b.*|.+?nonEnumArgs *=.+/g, ''); - // remove `nonEnumArgs` fix from `iteratorTemplate` + // remove `nonEnumArgs` from `_.keys` + source = source.replace(matchFunction(source, 'keys'), function(match) { + return match + .replace(/(?:\s*\|\|\s*)?\(nonEnumArgs[^)]+\)\)/, '') + .replace(/\s*if *\(\s*\)[^}]+}/, ''); + }); + + // remove `nonEnumArgs` from `iteratorTemplate` source = source.replace(getIteratorTemplate(source), function(match) { return match .replace(/(?: *\/\/.*\n)*( *["'] *)<% *} *else *if *\(nonEnumArgs[\s\S]+?(\1<% *} *%>.+)/, '$2') diff --git a/build/pre-compile.js b/build/pre-compile.js index 7b9dec296..5f6bd98e9 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -26,7 +26,6 @@ 'objectTypes', 'ownIndex', 'ownProps', - 'propertyIsEnumerable', 'result', 'skipProto', 'source', diff --git a/lodash.js b/lodash.js index 292cc0b60..eff245c2f 100644 --- a/lodash.js +++ b/lodash.js @@ -85,7 +85,6 @@ getPrototypeOf = reNative.test(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf, hasOwnProperty = objectRef.hasOwnProperty, push = arrayRef.push, - propertyIsEnumerable = objectRef.propertyIsEnumerable, toString = objectRef.toString; /* Native method shortcuts for methods with the same name as other `lodash` methods */ @@ -153,7 +152,7 @@ var hasObjectSpliceBug = (hasObjectSpliceBug = { '0': 1, 'length': 1 }, arrayRef.splice.call(hasObjectSpliceBug, 0, 1), hasObjectSpliceBug[0]); - /** Detect if an `arguments` object's indexes are non-enumerable (IE < 9) */ + /** Detect if `arguments` object indexes are non-enumerable (Firefox < 4, IE < 9, Safari < 5.1) */ var nonEnumArgs = true; (function() { @@ -164,7 +163,7 @@ for (prop in arguments) { nonEnumArgs = !prop; } hasDontEnumBug = !/valueOf/.test(props); - hasEnumPrototype = propertyIsEnumerable.call(ctor, 'prototype'); + hasEnumPrototype = ctor.propertyIsEnumerable('prototype'); iteratesOwnLast = props[0] != 'x'; }(1)); @@ -392,8 +391,7 @@ // avoid iterating over `prototype` properties in older Firefox, Opera, and Safari ' <% if (hasEnumPrototype) { %>\n' + - " var skipProto = typeof iterable == 'function' && \n" + - " propertyIsEnumerable.call(iterable, 'prototype');\n" + + " var skipProto = typeof iterable == 'function';\n" + ' <% } %>' + // iterate own properties using `Object.keys` if it's fast @@ -710,13 +708,13 @@ // create the function factory var factory = Function( 'createCallback, hasOwnProperty, isArguments, isArray, isString, ' + - 'objectTypes, nativeKeys, propertyIsEnumerable', + 'objectTypes, nativeKeys', 'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' ); // return the compiled function return factory( createCallback, hasOwnProperty, isArguments, isArray, isString, - objectTypes, nativeKeys, propertyIsEnumerable + objectTypes, nativeKeys ); } @@ -728,6 +726,7 @@ * iteration early by explicitly returning `false`. * * @private + * @type Function * @param {Array|Object|String} collection The collection to iterate over. * @param {Function} [callback=identity] The function called per iteration. * @param {Mixed} [thisArg] The `this` binding of `callback`. @@ -855,6 +854,7 @@ * * @static * @memberOf _ + * @type Function * @category Objects * @param {Object} object The object to iterate over. * @param {Function} [callback=identity] The function called per iteration. @@ -887,6 +887,7 @@ * * @static * @memberOf _ + * @type Function * @category Objects * @param {Object} object The object to iterate over. * @param {Function} [callback=identity] The function called per iteration. @@ -937,10 +938,14 @@ * // => ['one', 'two', 'three'] (order is not guaranteed) */ var keys = !nativeKeys ? shimKeys : function(object) { - // avoid iterating over the `prototype` property - return hasEnumPrototype && typeof object == 'function' && propertyIsEnumerable.call(object, 'prototype') - ? shimKeys(object) - : (isObject(object) ? nativeKeys(object) : []); + if (!isObject(object)) { + return []; + } + if ((hasEnumPrototype && typeof object == 'function') || + (nonEnumArgs && object.length && isArguments(object))) { + return shimKeys(object); + } + return nativeKeys(object); }; /** @@ -1027,6 +1032,7 @@ * * @static * @memberOf _ + * @type Function * @alias extend * @category Objects * @param {Object} object The destination object. @@ -1220,6 +1226,7 @@ * * @static * @memberOf _ + * @type Function * @category Objects * @param {Object} object The destination object. * @param {Object} [source1, source2, ...] The source objects.