diff --git a/build.js b/build.js index e72fd4673..45510c5e8 100755 --- a/build.js +++ b/build.js @@ -217,15 +217,14 @@ 'bottom', 'exit', 'firstArg', - 'hasExp', 'hasDontEnumBug', 'inLoop', 'init', 'isKeysFast', - 'iteratedObject', - 'loopExp', + 'iteratee', 'object', 'objectBranch', + 'noCharByIndex', 'shadowed', 'top', 'useHas' @@ -260,7 +259,7 @@ /*--------------------------------------------------------------------------*/ /** - * Gets the aliases associated with a given `funcName`. + * Gets the aliases associated with a given function name. * * @private * @param {String} funcName The name of the function to get aliases for. @@ -271,7 +270,7 @@ } /** - * Gets an array of depenants for a function by the given `funcName`. + * Gets an array of depenants for a function by a given name. * * @private * @param {String} funcName The name of the function to query. @@ -320,6 +319,8 @@ */ function getFunctionSource(func) { var source = func.source || (func + ''); + + // all leading whitespace return source.replace(/\n(?:.*)/g, function(match, index) { match = match.slice(1); return ( @@ -340,7 +341,7 @@ } /** - * Gets the real name, not alias, of a given `funcName`. + * Gets the real name, not alias, of a given function name. * * @private * @param {String} funcName The name of the function to resolve. @@ -393,7 +394,7 @@ } /** - * Removes the all references to `refName` from the `createIterator` source. + * Removes the all references to `refName` from `createIterator` in `source`. * * @private * @param {String} source The source to process. @@ -461,10 +462,10 @@ */ function removeKeysOptimization(source) { return removeVar(source, 'isKeysFast') - // remove conditional concat in `mapIteratorOptions` - .replace(/= *' *\+ *\(isKeysFast.+/, "= []'") - // remove conditional concat in `mapIteratorOptions`, `invoke`, `pluck`, and `sortBy` - .replace(/' *\+ *\(isKeysFast.+?\) *\+ *'/g, '.push') + // remove `isKeysFast` from `beforeLoop.object` of `mapIteratorOptions` + .replace(/=\s*'\s*\+\s*\(isKeysFast.+/, "= []'") + // remove `isKeysFast` from `inLoop.object` of `mapIteratorOptions`, `invoke`, `pluck`, and `sortBy` + .replace(/'\s*\+\s*\(isKeysFast[^)]+?\)\s*\+\s*'/g, '.push') // remove data object property assignment in `createIterator` .replace(/\s*.+?\.isKeysFast *=.+/, '') // remove optimized branch in `iteratorTemplate` @@ -518,7 +519,7 @@ '(?:.+?;|(?:Function\\(.+?|.*?[^,])\\n[\\s\\S]+?\\n\\2.+?;)\\n' ), '$1' + varValue + ';\n'); - // replace a varaible at the start of middle of a declaration list + // replace a varaible at the start or middle of a declaration list source = source.replace(RegExp('((?:var|\\n) +' + varName + ' *=).+?,'), '$1 ' + varValue + ','); // replace a variable at the end of a variable declaration list @@ -531,7 +532,7 @@ // Backbone build if (isBackbone) { - // add any additional dependencies + // add any additional sub-dependencies backboneDependencies = getDependencies(backboneDependencies); if (filterType == 'exclude') { @@ -569,14 +570,13 @@ includeMethods = lodash.without.apply(lodash, [categoryMethods].concat(excludeMethods)); } else if (filterType) { - // merge backbone dependencies into `includeMethods` + // merge `categoryMethods` into `includeMethods` includeMethods = lodash.union(includeMethods, categoryMethods); } else { - // include only the Backbone dependencies + // include only the `categoryMethods` includeMethods = categoryMethods; } - filterType = 'include'; return true; }); @@ -609,7 +609,7 @@ }); } - // remove associated functions, variables and code snippets + // remove associated functions, variables, and code snippets if (isRemoved(source, 'isArguments')) { source = removeIsArgumentsFallback(source); } @@ -674,7 +674,7 @@ /*--------------------------------------------------------------------------*/ - // DRY out isType methods + // DRY out isType functions (function() { var iteratorName = lodash.find(['forEach', 'forOwn'], function(funcName) { return !isRemoved(source, funcName); @@ -686,7 +686,7 @@ } var snippet, funcNames = [], - result = [], + objectSnippets = [], token = '__isTypeToken__'; // build replacement code @@ -698,7 +698,7 @@ 'RegExp': 'regexpClass', 'String': 'stringClass' }, function(value, key) { - // skip `isArguments` if legacy build + // skip `isArguments` if a legacy build if (isLegacy && key == 'Arguments') { return; } @@ -711,12 +711,12 @@ snippet = funcCode; } funcNames.push(funcName); - result.push("'" + key + "': " + value); + objectSnippets.push("'" + key + "': " + value); } }); - // skip this optimization if there are less than 2 functions - if (result.length < 2) { + // skip this optimization if there are less than 2 isType functions + if (funcNames.length < 2) { return; } // add a token to mark the position to insert new code @@ -730,7 +730,7 @@ // replace token with new DRY code source = source.replace(token, ' // add `_.' + funcNames.join('`, `_.') + '`\n' + - ' ' + iteratorName + '({\n ' + result.join(',\n ') + '\n }, function(className, key) {\n' + + ' ' + iteratorName + '({\n ' + objectSnippets.join(',\n ') + '\n }, function(className, key) {\n' + " lodash['is' + key] = function(value) {\n" + ' return toString.call(value) == className;\n' + ' };\n' + @@ -740,8 +740,8 @@ // tweak `isArguments` fallback snippet = !isLegacy && getIsArgumentsFallback(source); if (snippet) { - result = '\n' + snippet.replace(/isArguments/g, 'lodash.$&'); - source = source.replace(snippet, result); + var modified = '\n' + snippet.replace(/isArguments/g, 'lodash.$&'); + source = source.replace(snippet, modified); } }()); @@ -754,17 +754,17 @@ ['bind', 'isArray'].forEach(function(funcName) { var snippet = matchFunction(source, funcName), - result = snippet; + modified = snippet; - // remove native `Function#bind` branch + // remove native `Function#bind` branch in `_.bind` if (funcName == 'bind' ) { - result = result.replace(/(?:\s*\/\/.*)*\s*else if *\(isBindFast[^}]+}/, ''); + modified = modified.replace(/(?:\s*\/\/.*)*\s*else if *\(isBindFast[^}]+}/, ''); } - // remove native `Array.isArray` branch + // remove native `Array.isArray` branch in `_.isArray` else if (funcName == 'isArray') { - result = result.replace(/nativeIsArray * \|\|/, ''); + modified = modified.replace(/nativeIsArray * \|\|/, ''); } - source = source.replace(snippet, result); + source = source.replace(snippet, modified); }); // replace `_.keys` with `shimKeys` @@ -786,12 +786,13 @@ source = removeIsArgumentsFallback(source); } + source = removeVar(source, 'reNative'); source = removeFromCreateIterator(source, 'nativeKeys'); source = removeKeysOptimization(source); } if (isMobile) { - // inline functions defined with `createIterator` + // inline all functions defined with `createIterator` lodash.functions(lodash).forEach(function(funcName) { // match `funcName` with pseudo private `_` prefixes removed to allow matching `shimKeys` var reFunc = RegExp('(\\bvar ' + funcName.replace(/^_/, '') + ' *= *)createIterator\\(((?:{|[a-zA-Z])[\\s\\S]+?)\\);\\n'); @@ -804,12 +805,15 @@ source = source.replace(reFunc, '$1' + getFunctionSource(lodash[funcName]) + ';\n'); }); - // remove JScript [[DontEnum]] fix from `isEqual` + // remove JScript [[DontEnum]] fix from `_.isEqual` source = source.replace(/(?:\s*\/\/.*)*\n( +)if *\(result *&& *hasDontEnumBug[\s\S]+?\n\1}/, ''); - // remove IE `shift` and `splice` fix + // remove IE `shift` and `splice` fix from mutator Array functions mixin source = source.replace(/(?:\s*\/\/.*)*\n( +)if *\(value.length *=== *0[\s\S]+?\n\1}/, ''); + // remove `noCharByIndex` from `_.reduceRight` and `_.toArray` + source = source.replace(/noCharByIndex *&&[^:]+: *([^;]+)/g, '$1'); + source = removeVar(source, 'extendIteratorOptions'); source = removeVar(source, 'hasDontEnumBug'); source = removeVar(source, 'iteratorTemplate'); @@ -821,9 +825,16 @@ source = source.replace(/(( +)var iteratorTemplate *= *)[\s\S]+?\n\2.+?;\n/, (function() { var snippet = getFunctionSource(lodash._iteratorTemplate); - // expand properties to avoid having to use a with-statement + // prepend data object references to property names to avoid having to + // use a with-statement iteratorOptions.forEach(function(property) { - snippet = snippet.replace(RegExp('([^\\w.])\\b' + property + '\\b', 'g'), '$1obj.' + property); + if (property == 'iteratee') { + // use a more fine-grained regexp for the `iteratee` property because + // it's a compiled variable as well as a data property + snippet = snippet.replace(/(__t *= *\( *)(iteratee)/, '$1obj.$2'); + } else { + snippet = snippet.replace(RegExp('([^\\w.])\\b' + property + '\\b', 'g'), '$1obj.' + property); + } }); // remove unnecessary code diff --git a/build/pre-compile.js b/build/pre-compile.js index e14575f69..df050921a 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -20,6 +20,7 @@ 'identity', 'index', 'isFunc', + 'iteratee', 'iteratorBind', 'length', 'methodName', @@ -53,15 +54,14 @@ 'bottom', 'exit', 'firstArg', - 'hasExp', 'hasDontEnumBug', 'inLoop', 'init', 'isKeysFast', - 'iteratedObject', - 'loopExp', + 'iteratee', 'object', 'objectBranch', + 'noCharByIndex', 'shadowed', 'top', 'useHas' @@ -196,9 +196,9 @@ /*--------------------------------------------------------------------------*/ /** - * Pre-process a given JavaScript `source`, preparing it for minification. + * Pre-process a given Lo-Dash source, preparing it for minification. * - * @param {String} source The source to process. + * @param {String} source The Lo-Dash source to process. * @returns {String} Returns the processed source. */ function preprocess(source) { @@ -257,23 +257,23 @@ return; } snippets.forEach(function(snippet) { - var result = snippet, - isSortBy = /var sortBy\b/.test(result), - isInlined = !/\bcreateIterator\b/.test(result); + var modified = snippet, + isSortBy = /var sortBy\b/.test(modified), + isInlined = !/\bcreateIterator\b/.test(modified); // minify properties properties.forEach(function(property, index) { - // add quotes around properties in the inlined `sortBy` of the mobile + // add quotes around properties in the inlined `_.sortBy` of the mobile // build so Closure Compiler won't mung them if (isSortBy && isInlined) { - result = result + modified = modified .replace(RegExp('\\.' + property + '\\b', 'g'), "['" + minNames[index] + "']") .replace(RegExp('\\b' + property + ' *:', 'g'), "'" + minNames[index] + "':"); } - result = result.replace(RegExp('\\b' + property + '\\b', 'g'), minNames[index]); + modified = modified.replace(RegExp('\\b' + property + '\\b', 'g'), minNames[index]); }); // replace with modified snippet - source = source.replace(snippet, result); + source = source.replace(snippet, modified); }); }()); @@ -299,26 +299,26 @@ snippets.forEach(function(snippet, index) { var isCreateIterator = /function createIterator\b/.test(snippet), isIteratorTemplate = /var iteratorTemplate\b/.test(snippet), - result = snippet; + modified = snippet; // add brackets to whitelisted properties so Closure Compiler won't mung them - result = result.replace(RegExp('\\.(' + iteratorOptions.join('|') + ')\\b', 'g'), "['$1']"); + modified = modified.replace(RegExp('\\.(' + iteratorOptions.join('|') + ')\\b', 'g'), "['$1']"); if (isCreateIterator) { // replace with modified snippet early and clip snippet so other arguments // aren't minified - source = source.replace(snippet, result); - snippet = result = result.replace(/factory\([\s\S]+$/, ''); + source = source.replace(snippet, modified); + snippet = modified = modified.replace(/factory\([\s\S]+$/, ''); } // minify snippet variables / arguments compiledVars.forEach(function(variable, index) { // ensure properties in compiled strings aren't minified - result = result.replace(RegExp('([^.]\\b)' + variable + '\\b(?!\' *[\\]:])', 'g'), '$1' + minNames[index]); + modified = modified.replace(RegExp('([^.]\\b)' + variable + '\\b(?!\' *[\\]:])', 'g'), '$1' + minNames[index]); // correct `typeof x == 'object'` if (variable == 'object') { - result = result.replace(RegExp("(typeof [^']+')" + minNames[index] + "'", 'g'), "$1object'"); + modified = modified.replace(RegExp("(typeof [^']+')" + minNames[index] + "'", 'g'), "$1object'"); } }); @@ -326,26 +326,26 @@ iteratorOptions.forEach(function(property, index) { if (isIteratorTemplate) { // minify property names as interpolated template variables - result = result.replace(RegExp('\\b' + property + '\\b', 'g'), minNames[index]); + modified = modified.replace(RegExp('\\b' + property + '\\b', 'g'), minNames[index]); } else { if (property == 'array' || property == 'object') { // minify "array" and "object" sub property names - result = result.replace(RegExp("'" + property + "'( *[\\]:])", 'g'), "'" + minNames[index] + "'$1"); + modified = modified.replace(RegExp("'" + property + "'( *[\\]:])", 'g'), "'" + minNames[index] + "'$1"); } else { // minify property name strings - result = result.replace(RegExp("'" + property + "'", 'g'), "'" + minNames[index] + "'"); + modified = modified.replace(RegExp("'" + property + "'", 'g'), "'" + minNames[index] + "'"); // minify property names in regexps and accessors if (isCreateIterator) { - result = result.replace(RegExp('([\\.|/])' + property + '\\b' , 'g'), '$1' + minNames[index]); + modified = modified.replace(RegExp('([\\.|/])' + property + '\\b' , 'g'), '$1' + minNames[index]); } } } }); // replace with modified snippet - source = source.replace(snippet, result); + source = source.replace(snippet, modified); }); return source; diff --git a/lodash.js b/lodash.js index 64686bef8..9035cc0f1 100644 --- a/lodash.js +++ b/lodash.js @@ -128,6 +128,13 @@ */ var hasDontEnumBug = !propertyIsEnumerable.call({ 'valueOf': 0 }, 'valueOf'); + /** + * Detect support for accessing string characters by index: + * IE < 8 can't access characters by index and IE 8 can only access + * characters by index on string literals. + */ + var noCharByIndex = ('x'[0] + Object('x')[0]) != 'xx'; + /* Detect if `Function#bind` exists and is inferred to be fast (i.e. all but V8) */ var isBindFast = nativeBind && /\n|Opera/.test(nativeBind + toString.call(window.opera)); @@ -262,18 +269,27 @@ */ var iteratorTemplate = template( // assign the `result` variable an initial value - 'var index, result<% if (init) { %> = <%= init %><% } %>;\n' + + 'var result<% if (init) { %> = <%= init %><% } %>;\n' + // add code to exit early or do so if the first argument is falsey '<%= exit %>;\n' + // add code after the exit snippet but before the iteration branches '<%= top %>;\n' + + 'var index, iteratee = <%= iteratee %>;\n' + // the following branch is for iterating arrays and array-like objects '<% if (arrayBranch) { %>' + - 'var length = <%= firstArg %>.length; index = -1;' + - ' <% if (objectBranch) { %>\nif (length === length >>> 0) {<% } %>\n' + + 'var length = iteratee.length; index = -1;' + + ' <% if (objectBranch) { %>\nif (length === length >>> 0) {<% } %>' + + + // add support for accessing string characters by index if needed + ' <% if (noCharByIndex) { %>\n' + + ' if (toString.call(iteratee) == stringClass) {\n' + + ' iteratee = iteratee.split(\'\')\n' + + ' }' + + ' <% } %>\n' + + ' <%= arrayBranch.beforeLoop %>;\n' + - ' while (<%= arrayBranch.loopExp %>) {\n' + + ' while (++index < length) {\n' + ' <%= arrayBranch.inLoop %>\n' + ' }' + ' <% if (objectBranch) { %>\n}<% } %>' + @@ -283,13 +299,13 @@ '<% if (objectBranch) { %>' + ' <% if (arrayBranch) { %>\nelse {<% } %>' + ' <% if (!hasDontEnumBug) { %>\n' + - ' var skipProto = typeof <%= iteratedObject %> == \'function\' && \n' + - ' propertyIsEnumerable.call(<%= iteratedObject %>, \'prototype\');\n' + + ' var skipProto = typeof iteratee == \'function\' && \n' + + ' propertyIsEnumerable.call(iteratee, \'prototype\');\n' + ' <% } %>' + // iterate own properties using `Object.keys` if it's fast ' <% if (isKeysFast && useHas) { %>\n' + - ' var props = nativeKeys(<%= iteratedObject %>),\n' + + ' var props = nativeKeys(iteratee),\n' + ' propIndex = -1,\n' + ' length = props.length;\n\n' + ' <%= objectBranch.beforeLoop %>;\n' + @@ -303,11 +319,11 @@ // else using a for-in loop ' <% } else { %>\n' + ' <%= objectBranch.beforeLoop %>;\n' + - ' for (<%= objectBranch.loopExp %>) {' + + ' for (index in iteratee) {' + ' <% if (hasDontEnumBug) { %>\n' + - ' <% if (useHas) { %>if (<%= hasExp %>) {\n <% } %>' + + ' <% if (useHas) { %>if (hasOwnProperty.call(iteratee, index)) {\n <% } %>' + ' <%= objectBranch.inLoop %>;\n' + - ' <% if (useHas) { %>}<% } %>' + + ' <% if (useHas) { %>}<% } %>' + ' <% } else { %>\n' + // Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 @@ -316,7 +332,8 @@ // value to `true`. Because of this Lo-Dash standardizes on skipping // the the `prototype` property of functions regardless of its // [[Enumerable]] value. - ' if (!(skipProto && index == \'prototype\')<% if (useHas) { %> && <%= hasExp %><% } %>) {\n' + + ' if (!(skipProto && index == \'prototype\')<% if (useHas) { %> &&\n' + + ' hasOwnProperty.call(iteratee, index)<% } %>) {\n' + ' <%= objectBranch.inLoop %>\n' + ' }' + ' <% } %>\n' + @@ -328,13 +345,13 @@ // defaults to non-enumerable, Lo-Dash skips the `constructor` // property when it infers it's iterating over a `prototype` object. ' <% if (hasDontEnumBug) { %>\n\n' + - ' var ctor = <%= iteratedObject %>.constructor;\n' + + ' var ctor = iteratee.constructor;\n' + ' <% for (var k = 0; k < 7; k++) { %>\n' + ' index = \'<%= shadowed[k] %>\';\n' + ' if (<%' + ' if (shadowed[k] == \'constructor\') {' + - ' %>!(ctor && ctor.prototype === <%= iteratedObject %>) && <%' + - ' } %><%= hasExp %>) {\n' + + ' %>!(ctor && ctor.prototype === iteratee) && <%' + + ' } %>hasOwnProperty.call(iteratee, index)) {\n' + ' <%= objectBranch.inLoop %>\n' + ' }' + ' <% } %>' + @@ -363,13 +380,13 @@ 'else if (thisArg) {\n' + ' callback = iteratorBind(callback, thisArg)\n' + '}', - 'inLoop': 'callback(collection[index], index, collection)' + 'inLoop': 'callback(iteratee[index], index, collection)' }; /** Reusable iterator options for `every` and `some` */ var everyIteratorOptions = { 'init': 'true', - 'inLoop': 'if (!callback(collection[index], index, collection)) return !result' + 'inLoop': 'if (!callback(iteratee[index], index, collection)) return !result' }; /** Reusable iterator options for `defaults` and `extend` */ @@ -380,16 +397,16 @@ 'for (var source, sourceIndex = 1, length = arguments.length; sourceIndex < length; sourceIndex++) {\n' + ' source = arguments[sourceIndex];\n' + (hasDontEnumBug ? ' if (source) {' : ''), - 'loopExp': 'index in source', + 'iteratee': 'source', 'useHas': false, - 'inLoop': 'object[index] = source[index]', + 'inLoop': 'result[index] = iteratee[index]', 'bottom': (hasDontEnumBug ? ' }\n' : '') + '}' }; /** Reusable iterator options for `filter` and `reject` */ var filterIteratorOptions = { 'init': '[]', - 'inLoop': 'callback(collection[index], index, collection) && result.push(collection[index])' + 'inLoop': 'callback(iteratee[index], index, collection) && result.push(iteratee[index])' }; /** Reusable iterator options for `find`, `forEach`, `forIn`, and `forOwn` */ @@ -413,8 +430,8 @@ 'object': 'result = ' + (isKeysFast ? 'Array(length)' : '[]') }, 'inLoop': { - 'array': 'result[index] = callback(collection[index], index, collection)', - 'object': 'result' + (isKeysFast ? '[propIndex] = ' : '.push') + '(callback(collection[index], index, collection))' + 'array': 'result[index] = callback(iteratee[index], index, collection)', + 'object': 'result' + (isKeysFast ? '[propIndex] = ' : '.push') + '(callback(iteratee[index], index, collection))' } }; @@ -442,8 +459,8 @@ * beforeLoop - A string or object containing an "array" or "object" property * of code to execute before the array or object loops. * - * loopExp - A string or object containing an "array" or "object" property - * of code to execute as the array or object loop expression. + * iteratee - A string or object containing an "array" or "object" property + * of the variable to be iterated in the loop expression. * * useHas - A boolean to specify whether or not to use `hasOwnProperty` checks * in the object loop. @@ -469,7 +486,7 @@ 'exit': '', 'init': '', 'top': '', - 'arrayBranch': { 'beforeLoop': '', 'loopExp': '++index < length' }, + 'arrayBranch': { 'beforeLoop': '' }, 'objectBranch': { 'beforeLoop': '' } }; @@ -478,7 +495,7 @@ for (prop in object) { value = (value = object[prop]) == null ? '' : value; // keep this regexp explicit for the build pre-process - if (/beforeLoop|loopExp|inLoop/.test(prop)) { + if (/beforeLoop|inLoop/.test(prop)) { if (typeof value == 'string') { value = { 'array': value, 'object': value }; } @@ -491,29 +508,22 @@ } // set additional template `data` values var args = data.args, - arrayBranch = data.arrayBranch, - objectBranch = data.objectBranch, firstArg = /^[^,]+/.exec(args)[0], - loopExp = objectBranch.loopExp, - iteratedObject = /\S+$/.exec(loopExp || firstArg)[0]; + iteratee = (data.iteratee = data.iteratee || firstArg); data.firstArg = firstArg; data.hasDontEnumBug = hasDontEnumBug; - data.hasExp = 'hasOwnProperty.call(' + iteratedObject + ', index)'; data.isKeysFast = isKeysFast; - data.iteratedObject = iteratedObject; + data.noCharByIndex = noCharByIndex; data.shadowed = shadowed; data.useHas = data.useHas !== false; if (!data.exit) { data.exit = 'if (!' + firstArg + ') return result'; } - if (firstArg == 'object' || !arrayBranch.inLoop) { + if (firstArg != 'collection' || !data.arrayBranch.inLoop) { data.arrayBranch = null; } - if (!loopExp) { - objectBranch.loopExp = 'index in ' + iteratedObject; - } // create the function factory var factory = Function( 'arrayClass, compareAscending, funcClass, hasOwnProperty, identity, ' + @@ -620,7 +630,7 @@ */ var shimKeys = createIterator({ 'args': 'object', - 'exit': 'if (!objectTypes[typeof object] || object === null) throw TypeError()', + 'exit': 'if (!(object && objectTypes[typeof object])) throw TypeError()', 'init': '[]', 'inLoop': 'result.push(index)' }); @@ -694,7 +704,7 @@ * @memberOf _ * @alias include * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @param {Array|Object|String} collection The collection to iterate over. * @param {Mixed} target The value to check for. * @returns {Boolean} Returns `true` if `target` value is found, else `false`. * @example @@ -705,7 +715,7 @@ var contains = createIterator({ 'args': 'collection, target', 'init': 'false', - 'inLoop': 'if (collection[index] === target) return true' + 'inLoop': 'if (iteratee[index] === target) return true' }); /** @@ -718,7 +728,7 @@ * @memberOf _ * @alias all * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @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 for the callback. * @returns {Boolean} Returns `true` if all values pass the callback check, else `false`. @@ -739,7 +749,7 @@ * @memberOf _ * @alias select * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @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 for the callback. * @returns {Array} Returns a new array of values that passed callback check. @@ -761,7 +771,7 @@ * @memberOf _ * @alias detect * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @param {Array|Object|String} collection The collection to iterate over. * @param {Function} callback The function called per iteration. * @param {Mixed} [thisArg] The `this` binding for the callback. * @returns {Mixed} Returns the value that passed the callback check, else `undefined`. @@ -772,7 +782,7 @@ */ var find = createIterator(baseIteratorOptions, forEachIteratorOptions, { 'init': '', - 'inLoop': 'if (callback(collection[index], index, collection)) return collection[index]' + 'inLoop': 'if (callback(iteratee[index], index, collection)) return iteratee[index]' }); /** @@ -785,7 +795,7 @@ * @memberOf _ * @alias each * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @param {Array|Object|String} collection The collection to iterate over. * @param {Function} callback The function called per iteration. * @param {Mixed} [thisArg] The `this` binding for the callback. * @returns {Array|Object} Returns the `collection`. @@ -809,7 +819,7 @@ * @static * @memberOf _ * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @param {Array|Object|String} collection The collection to iterate over. * @param {Function|String} callback The function called per iteration or * property name to group by. * @param {Mixed} [thisArg] The `this` binding for the callback. @@ -832,9 +842,9 @@ 'if (isFunc && thisArg) callback = iteratorBind(callback, thisArg)', 'inLoop': 'prop = isFunc\n' + - ' ? callback(collection[index], index, collection)\n' + - ' : collection[index][callback];\n' + - '(hasOwnProperty.call(result, prop) ? result[prop] : result[prop] = []).push(collection[index])' + ' ? callback(iteratee[index], index, collection)\n' + + ' : iteratee[index][callback];\n' + + '(hasOwnProperty.call(result, prop) ? result[prop] : result[prop] = []).push(iteratee[index])' }); /** @@ -846,7 +856,7 @@ * @static * @memberOf _ * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @param {Array|Object|String} collection The collection to iterate over. * @param {Function|String} methodName The name of the method to invoke or * the function invoked per iteration. * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with. @@ -865,8 +875,12 @@ 'var args = slice.call(arguments, 2),\n' + ' isFunc = typeof methodName == \'function\'', 'inLoop': { - 'array': 'result[index] = (isFunc ? methodName : collection[index][methodName]).apply(collection[index], args)', - 'object': 'result' + (isKeysFast ? '[propIndex] = ' : '.push') + '((isFunc ? methodName : collection[index][methodName]).apply(collection[index], args))' + 'array': + 'result[index] = (isFunc ? methodName : iteratee[index][methodName])' + + '.apply(iteratee[index], args)', + 'object': + 'result' + (isKeysFast ? '[propIndex] = ' : '.push') + + '((isFunc ? methodName : iteratee[index][methodName]).apply(iteratee[index], args))' } }); @@ -880,7 +894,7 @@ * @memberOf _ * @alias collect * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @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 for the callback. * @returns {Array} Returns a new array of values returned by the callback. @@ -901,7 +915,7 @@ * @static * @memberOf _ * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @param {Array|Object|String} collection The collection to iterate over. * @param {String} property The property to pluck. * @returns {Array} Returns a new array of property values. * @example @@ -918,8 +932,8 @@ var pluck = createIterator(mapIteratorOptions, { 'args': 'collection, property', 'inLoop': { - 'array': 'result[index] = collection[index][property]', - 'object': 'result' + (isKeysFast ? '[propIndex] = ' : '.push') + '(collection[index][property])' + 'array': 'result[index] = iteratee[index][property]', + 'object': 'result' + (isKeysFast ? '[propIndex] = ' : '.push') + '(iteratee[index][property])' } }); @@ -934,7 +948,7 @@ * @memberOf _ * @alias foldl, inject * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @param {Array|Object|String} collection The collection to iterate over. * @param {Function} callback The function called per iteration. * @param {Mixed} [accumulator] Initial value of the accumulator. * @param {Mixed} [thisArg] The `this` binding for the callback. @@ -955,11 +969,11 @@ }, 'inLoop': { 'array': - 'result = callback(result, collection[index], index, collection)', + 'result = callback(result, iteratee[index], index, collection)', 'object': 'result = noaccum\n' + - ' ? (noaccum = false, collection[index])\n' + - ' : callback(result, collection[index], index, collection)' + ' ? (noaccum = false, iteratee[index])\n' + + ' : callback(result, iteratee[index], index, collection)' } }); @@ -970,7 +984,7 @@ * @memberOf _ * @alias foldr * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @param {Array|Object|String} collection The collection to iterate over. * @param {Function} callback The function called per iteration. * @param {Mixed} [accumulator] Initial value of the accumulator. * @param {Mixed} [thisArg] The `this` binding for the callback. @@ -993,11 +1007,15 @@ callback = iteratorBind(callback, thisArg); } if (length === length >>> 0) { + var iteratee = noCharByIndex && toString.call(collection) == stringClass + ? collection.split('') + : collection; + if (length && noaccum) { - accumulator = collection[--length]; + accumulator = iteratee[--length]; } while (length--) { - accumulator = callback(accumulator, collection[length], length, collection); + accumulator = callback(accumulator, iteratee[length], length, collection); } return accumulator; } @@ -1023,7 +1041,7 @@ * @static * @memberOf _ * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @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 for the callback. * @returns {Array} Returns a new array of values that did **not** pass the callback check. @@ -1047,7 +1065,7 @@ * @memberOf _ * @alias any * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @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 for the callback. * @returns {Boolean} Returns `true` if any value passes the callback check, else `false`. @@ -1073,7 +1091,7 @@ * @static * @memberOf _ * @category Collections - * @param {Array|Object} collection The collection to iterate over. + * @param {Array|Object|String} collection The collection to iterate over. * @param {Function|String} callback The function called per iteration or * property name to sort by. * @param {Mixed} [thisArg] The `this` binding for the callback. @@ -1101,13 +1119,13 @@ 'inLoop': { 'array': 'result[index] = {\n' + - ' criteria: callback(collection[index], index, collection),\n' + - ' value: collection[index]\n' + + ' criteria: callback(iteratee[index], index, collection),\n' + + ' value: iteratee[index]\n' + '}', 'object': 'result' + (isKeysFast ? '[propIndex] = ' : '.push') + '({\n' + - ' criteria: callback(collection[index], index, collection),\n' + - ' value: collection[index]\n' + + ' criteria: callback(iteratee[index], index, collection),\n' + + ' value: iteratee[index]\n' + '})' }, 'bottom': @@ -1125,7 +1143,7 @@ * @static * @memberOf _ * @category Collections - * @param {Array|Object} collection The collection to convert. + * @param {Array|Object|String} collection The collection to convert. * @returns {Array} Returns the new converted array. * @example * @@ -1141,7 +1159,9 @@ } var length = collection.length; if (length === length >>> 0) { - return slice.call(collection); + return noCharByIndex && toString.call(collection) == stringClass + ? collection.split('') + : slice.call(collection); } return values(collection); } @@ -2008,7 +2028,7 @@ // mimic the constructor's `return` behavior // http://es5.github.com/#x13.2.2 var result = func.apply(thisBinding, args); - return objectTypes[typeof result] && result !== null + return result && objectTypes[typeof result] ? result : thisBinding } @@ -2366,7 +2386,7 @@ * // => { 'name': 'moe' }; */ function clone(value) { - return objectTypes[typeof value] && value !== null + return value && objectTypes[typeof value] ? (isArray(value) ? value.slice() : extend({}, value)) : value; } @@ -2389,7 +2409,7 @@ * // => { 'flavor': 'chocolate', 'sprinkles': 'rainbow' } */ var defaults = createIterator(extendIteratorOptions, { - 'inLoop': 'if (object[index] == null)' + extendIteratorOptions.inLoop + 'inLoop': 'if (result[index] == null) ' + extendIteratorOptions.inLoop }); /** @@ -2480,7 +2500,7 @@ 'args': 'object', 'init': '[]', 'useHas': false, - 'inLoop': 'if (toString.call(object[index]) == funcClass) result.push(index)', + 'inLoop': 'if (toString.call(iteratee[index]) == funcClass) result.push(index)', 'bottom': 'result.sort()' }); @@ -2853,7 +2873,7 @@ function isObject(value) { // check if the value is the ECMAScript language type of Object // http://es5.github.com/#x8 - return objectTypes[typeof value] && value !== null; + return value && objectTypes[typeof value]; } /** @@ -3075,7 +3095,7 @@ var values = createIterator({ 'args': 'object', 'init': '[]', - 'inLoop': 'result.push(object[index])' + 'inLoop': 'result.push(iteratee[index])' }); /*--------------------------------------------------------------------------*/ @@ -3274,21 +3294,21 @@ startIndex, result, useWith, - defaults = lodash.templateSettings, escapeDelimiter = options.escape, evaluateDelimiter = options.evaluate, interpolateDelimiter = options.interpolate, + settings = lodash.templateSettings, variable = options.variable; - // use template defaults if no option is provided + // use default settings if no options object is provided if (escapeDelimiter == null) { - escapeDelimiter = defaults.escape; + escapeDelimiter = settings.escape; } if (evaluateDelimiter == null) { - evaluateDelimiter = defaults.evaluate; + evaluateDelimiter = settings.evaluate; } if (interpolateDelimiter == null) { - interpolateDelimiter = defaults.interpolate; + interpolateDelimiter = settings.interpolate; } // tokenize delimiters to avoid escaping them @@ -3314,7 +3334,7 @@ // delimiters, inject a with-statement around all "evaluate" delimiters to // add the data object to the top of the scope chain if (!variable) { - variable = defaults.variable || lastVariable || 'obj'; + variable = settings.variable || lastVariable || 'obj'; useWith = isEvaluating; if (useWith) { @@ -3349,7 +3369,7 @@ lastVariable = variable; reDoubleVariable = RegExp('([(\\s])' + variable + '\\.' + variable + '\\b', 'g'); } - // inject data object references outside of the with-statement + // prepend data object references to property names outside of the with-statement text = (useWith ? text.slice(0, startIndex) : text) .replace(reInsertVariable, strInsertVariable) .replace(reDoubleVariable, strDoubleVariable) +