diff --git a/build.js b/build.js index dd877e1d1..f51ea2099 100755 --- a/build.js +++ b/build.js @@ -73,7 +73,7 @@ 'clone': ['assign', 'forEach', 'forOwn', 'isArray', 'isObject'], 'compact': [], 'compose': [], - 'contains': ['forEach', 'indexOf', 'isString'], + 'contains': ['indexOf', 'isString'], 'countBy': ['forEach'], 'debounce': [], 'defaults': ['isArguments'], @@ -81,12 +81,12 @@ 'delay': [], 'difference': ['indexOf'], 'escape': [], - 'every': ['forEach', 'isArray'], - 'filter': ['forEach', 'isArray'], + 'every': ['isArray'], + 'filter': ['isArray'], 'find': ['forEach'], 'first': [], 'flatten': ['isArray'], - 'forEach': ['identity', 'isArguments', 'isString'], + 'forEach': ['identity', 'isArguments', 'isArray', 'isString'], 'forIn': ['identity', 'isArguments'], 'forOwn': ['identity', 'isArguments'], 'functions': ['forIn', 'isFunction'], @@ -95,7 +95,7 @@ 'identity': [], 'indexOf': ['sortedIndex'], 'initial': [], - 'intersection': ['filter', 'indexOf'], + 'intersection': ['forEach', 'indexOf'], 'invert': ['forOwn'], 'invoke': ['forEach'], 'isArguments': [], @@ -118,11 +118,11 @@ 'keys': ['forOwn', 'isArguments', 'isObject'], 'last': [], 'lastIndexOf': [], - 'map': ['forEach', 'isArray'], - 'max': ['forEach', 'isArray', 'isString'], + 'map': ['isArray'], + 'max': ['isArray', 'isString'], 'memoize': [], 'merge': ['forOwn', 'isArray', 'isPlainObject'], - 'min': ['forEach', 'isArray', 'isString'], + 'min': ['isArray', 'isString'], 'mixin': ['forEach', 'forOwn', 'functions'], 'noConflict': [], 'object': [], @@ -134,14 +134,14 @@ 'pluck': ['map'], 'random': [], 'range': [], - 'reduce': ['forEach', 'isArray'], + 'reduce': ['isArray'], 'reduceRight': ['forEach', 'isString', 'keys'], 'reject': ['filter'], 'rest': [], 'result': ['isFunction'], 'shuffle': ['forEach'], 'size': ['keys'], - 'some': ['forEach', 'isArray'], + 'some': ['isArray'], 'sortBy': ['forEach'], 'sortedIndex': ['identity'], 'tap': ['mixin'], @@ -368,10 +368,10 @@ }); // replace wrapper `Array` method assignments - source = source.replace(/^(?: *\/\/.*\n)*( *)forEach\(\['[\s\S]+?\n\1}$/m, function() { + source = source.replace(/^(?: *\/\/.*\n)*( *)each\(\['[\s\S]+?\n\1}$/m, function() { return [ ' // add `Array` mutator functions to the wrapper', - " forEach(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {", + " each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {", ' var func = arrayRef[methodName];', ' lodash.prototype[methodName] = function() {', ' var value = this.__wrapped__;', @@ -387,7 +387,7 @@ ' });', '', ' // add `Array` accessor functions to the wrapper', - " forEach(['concat', 'join', 'slice'], function(methodName) {", + " each(['concat', 'join', 'slice'], function(methodName) {", ' var func = arrayRef[methodName];', ' lodash.prototype[methodName] = function() {', ' var value = this.__wrapped__,', @@ -1274,11 +1274,11 @@ dependencyMap.reduceRight = ['forEach', 'keys']; } if (isUnderscore) { - dependencyMap.contains = ['forEach', 'indexOf']; + dependencyMap.contains = ['indexOf']; dependencyMap.isEqual = ['isArray', 'isFunction']; dependencyMap.isEmpty = ['isArray', 'isString']; - dependencyMap.max = ['forEach', 'isArray']; - dependencyMap.min = ['forEach', 'isArray']; + dependencyMap.max = ['isArray']; + dependencyMap.min = ['isArray']; dependencyMap.pick = []; dependencyMap.template = ['defaults', 'escape']; @@ -1390,7 +1390,7 @@ " if (typeof length == 'number') {", ' result = indexOf(collection, target) > -1;', ' } else {', - ' forEach(collection, function(value) {', + ' each(collection, function(value) {', ' return (result = value === target) && indicatorObject;', ' });', ' }', @@ -1734,7 +1734,8 @@ if (isMobile) { // inline all functions defined with `createIterator` _.functions(lodash).forEach(function(methodName) { - var reFunc = RegExp('(\\bvar ' + methodName + ' *= *)createIterator\\(((?:{|[a-zA-Z])[\\s\\S]+?)\\);\\n'); + // strip leading underscores to match pseudo private functions + var reFunc = RegExp('(\\bvar ' + methodName.replace(/^_/, '') + ' *= *)createIterator\\(((?:{|[a-zA-Z])[\\s\\S]+?)\\);\\n'); if (reFunc.test(source)) { // extract, format, and inject the compiled function's source code source = source.replace(reFunc, function(match, captured) { @@ -1777,13 +1778,15 @@ }); }()); - // remove chainability from `_.forEach` - source = source.replace(matchFunction(source, 'forEach'), function(match) { - return match.replace(/return result([};\s]+)$/, '$1'); + // remove chainability from `each` and `_.forEach` + _.each(['each', 'forEach'], function(methodName) { + source = source.replace(matchFunction(source, methodName), function(match) { + return match.replace(/\n *return .+?([};\s]+)$/, '$1'); + }); }); - // unexpose "exit early" feature from `_.forEach`, `_.forIn`, and `_.forOwn` - _.each(['forEach', 'forIn', 'forOwn'], function(methodName) { + // unexpose "exit early" feature of `each`, `_.forEach`, `_.forIn`, and `_.forOwn` + _.each(['each', 'forEach', 'forIn', 'forOwn'], function(methodName) { source = source.replace(matchFunction(source, methodName), function(match) { return match.replace(/=== *false\)/g, '=== indicatorObject)'); }); @@ -1962,7 +1965,7 @@ // remove all `lodash.prototype` additions source = source .replace(/(?:\s*\/\/.*)*\n( *)forOwn\(lodash, *function\(func, *methodName\)[\s\S]+?\n\1}.+/g, '') - .replace(/(?:\s*\/\/.*)*\n( *)forEach\(\['[\s\S]+?\n\1}.+/g, '') + .replace(/(?:\s*\/\/.*)*\n( *)each\(\['[\s\S]+?\n\1}.+/g, '') .replace(/(?:\s*\/\/.*)*\s*lodash\.prototype.+\n/g, '') .replace(/(?:\s*\/\/.*)*\s*mixin\(lodash\).+\n/, ''); } diff --git a/lodash.js b/lodash.js index 5d58a9181..eb7781b8a 100644 --- a/lodash.js +++ b/lodash.js @@ -116,8 +116,7 @@ stringClass = '[object String]'; /** Detect various environments */ - var isFirefox = !/1/.test(Function('1')), - isIeOpera = !!window.attachEvent, + var isIeOpera = !!window.attachEvent, isV8 = nativeBind && !/\n|true/.test(nativeBind + isIeOpera); /* Detect if `Function#bind` exists and is inferred to be fast (all but V8) */ @@ -247,24 +246,25 @@ * method chaining. * * The chainable wrapper functions are: - * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`, - * `compose`, `countBy`, `debounce`, `defaults`, `defer`, `delay`, `difference`, + * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`, `compose`, + * `concat`, `countBy`, `debounce`, `defaults`, `defer`, `delay`, `difference`, * `filter`, `flatten`, `forEach`, `forIn`, `forOwn`, `functions`, `groupBy`, * `initial`, `intersection`, `invert`, `invoke`, `keys`, `map`, `max`, `memoize`, * `merge`, `min`, `object`, `omit`, `once`, `pairs`, `partial`, `pick`, `pluck`, - * `range`, `reject`, `rest`, `shuffle`, `sortBy`, `tap`, `throttle`, `times`, - * `toArray`, `union`, `uniq`, `values`, `where`, `without`, `wrap`, and `zip` + * `push`, `range`, `reject`, `rest`, `reverse`, `shuffle`, `slice`, `sort`, + * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `union`, `uniq`, + * `unshift`, `values`, `where`, `without`, `wrap`, and `zip` * * The non-chainable wrapper functions are: * `clone`, `contains`, `escape`, `every`, `find`, `has`, `identity`, `indexOf`, * `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`, `isEmpty`, * `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`, `isObject`, - * `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `lastIndexOf`, `mixin`, - * `noConflict`, `random`, `reduce`, `reduceRight`, `result`, `size`, `some`, - * `sortedIndex`, `template`, `unescape`, and `uniqueId` + * `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`, `lastIndexOf`, + * `mixin`, `noConflict`, `pop`, `random`, `reduce`, `reduceRight`, `result`, + * `shift`, `size`, `some`, `sortedIndex`, `template`, `unescape`, and `uniqueId` * * The wrapper functions `first` and `last` return wrapped values when `n` is - * passed, otherwise return unwrapped values. + * passed, otherwise they return unwrapped values. * * @name _ * @constructor @@ -334,25 +334,6 @@ /*--------------------------------------------------------------------------*/ - /** - * Creates a function from the given `args` and `body` strings. - * - * @private - * @param {String} args The comma separated function arguments. - * @param {String} body The function body. - * @returns {Function} The new function. - */ - function createFunction(args, body) { - // the newline, in `'\n}'`, is required to avoid errors if `body` ends - // with a single line comment - return window.eval('(function(' + args + ') {' + body + '\n})'); - } - // use `eval` to avoid Firefox's unoptimized `Function` constructor - // http://bugzil.la/804933 - if (isIeOpera || isV8 || !isFirefox) { - createFunction = Function; - } - /** * The template used to create iterator functions. * @@ -476,9 +457,9 @@ }; /** - * Reusable iterator options shared by `forEach`, `forIn`, and `forOwn`. + * Reusable iterator options shared by `each`, `forIn`, and `forOwn`. */ - var forEachIteratorOptions = { + var eachIteratorOptions = { 'args': 'collection, callback, thisArg', 'top': "callback = callback && typeof thisArg == 'undefined' ? callback : createCallback(callback, thisArg)", 'arrayLoop': 'if (callback(iteratee[index], index, collection) === false) return result', @@ -696,7 +677,7 @@ data.firstArg = /^[^,]+/.exec(args)[0]; // create the function factory - var factory = createFunction( + var factory = Function( 'createCallback, hasOwnProperty, isArguments, isString, objectTypes, ' + 'nativeKeys, propertyIsEnumerable', 'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' @@ -708,6 +689,21 @@ ); } + /** + * A function compiled to iterate `arguments` objects, arrays, objects, and + * strings consistenly across environments, executing the `callback` for each + * element in the `collection`. The `callback` is bound to `thisArg` and invoked + * with three arguments; (value, index|key, collection). Callbacks may exit + * iteration early by explicitly returning `false`. + * + * @private + * @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`. + * @returns {Array|Object|String} Returns `collection`. + */ + var each = createIterator(eachIteratorOptions); + /** * Used by `template` to escape characters for inclusion in compiled * string literals. @@ -867,7 +863,7 @@ * }); * // => alerts 'name' and 'bark' (order is not guaranteed) */ - var forIn = createIterator(forEachIteratorOptions, forOwnIteratorOptions, { + var forIn = createIterator(eachIteratorOptions, forOwnIteratorOptions, { 'useHas': false }); @@ -891,7 +887,7 @@ * }); * // => alerts '0', '1', and 'length' (order is not guaranteed) */ - var forOwn = createIterator(forEachIteratorOptions, forOwnIteratorOptions); + var forOwn = createIterator(eachIteratorOptions, forOwnIteratorOptions); /** * A fallback implementation of `isPlainObject` that checks if a given `value` @@ -1941,7 +1937,7 @@ : indexOf(collection, target, fromIndex) ) > -1; } else { - forEach(collection, function(value) { + each(collection, function(value) { if (++index >= fromIndex) { return !(result = value === target); } @@ -2020,7 +2016,7 @@ } } } else { - forEach(collection, function(value, index, collection) { + each(collection, function(value, index, collection) { return (result = !!callback(value, index, collection)); }); } @@ -2060,7 +2056,7 @@ } } } else { - forEach(collection, function(value, index, collection) { + each(collection, function(value, index, collection) { if (callback(value, index, collection)) { result.push(value); } @@ -2124,7 +2120,24 @@ * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, alert); * // => alerts each number value (order is not guaranteed) */ - var forEach = createIterator(forEachIteratorOptions); + function forEach(collection, callback, thisArg) { + if (isArray(collection)) { + var index = -1, + length = collection.length; + + if (!callback || typeof thisArg != 'undefined') { + callback = createCallback(callback, thisArg); + } + while (++index < length) { + if (callback(collection[index], index, collection) === false) { + break; + } + } + } else { + each(collection, callback, thisArg); + } + return collection; + } /** * Creates an object composed of keys returned from running each element of @@ -2228,7 +2241,7 @@ result[index] = callback(collection[index], index, collection); } } else { - forEach(collection, function(value, key, collection) { + each(collection, function(value, key, collection) { result[++index] = callback(value, key, collection); }); } @@ -2270,7 +2283,7 @@ ? charAtCallback : createCallback(callback, thisArg); - forEach(collection, function(value, index, collection) { + each(collection, function(value, index, collection) { var current = callback(value, index, collection); if (current > computed) { computed = current; @@ -2316,7 +2329,7 @@ ? charAtCallback : createCallback(callback, thisArg); - forEach(collection, function(value, index, collection) { + each(collection, function(value, index, collection) { var current = callback(value, index, collection); if (current < computed) { computed = current; @@ -2393,7 +2406,7 @@ accumulator = callback(accumulator, collection[index], index, collection); } } else { - forEach(collection, function(value, index, collection) { + each(collection, function(value, index, collection) { accumulator = noaccum ? (noaccum = false, value) : callback(accumulator, value, index, collection) @@ -2550,7 +2563,7 @@ } } } else { - forEach(collection, function(value, index, collection) { + each(collection, function(value, index, collection) { return !(result = callback(value, index, collection)); }); } @@ -4006,7 +4019,7 @@ : ''; try { - result = createFunction('_', 'return ' + source + sourceURL)(lodash); + result = Function('_', 'return ' + source + sourceURL)(lodash); } catch(e) { e.source = source; throw e; @@ -4136,7 +4149,7 @@ * // => '1,2,3' */ function wrapperToString() { - return String(this.__wrapped__); + return this.__wrapped__ + ''; } /** @@ -4323,7 +4336,7 @@ lodash.prototype.valueOf = wrapperValueOf; // add `Array` functions that return unwrapped values - forEach(['join', 'pop', 'shift'], function(methodName) { + each(['join', 'pop', 'shift'], function(methodName) { var func = arrayRef[methodName]; lodash.prototype[methodName] = function() { return func.apply(this.__wrapped__, arguments); @@ -4331,7 +4344,7 @@ }); // add `Array` functions that return the wrapped value - forEach(['push', 'reverse', 'sort', 'unshift'], function(methodName) { + each(['push', 'reverse', 'sort', 'unshift'], function(methodName) { var func = arrayRef[methodName]; lodash.prototype[methodName] = function() { func.apply(this.__wrapped__, arguments); @@ -4340,7 +4353,7 @@ }); // add `Array` functions that return new wrapped values - forEach(['concat', 'slice', 'splice'], function(methodName) { + each(['concat', 'slice', 'splice'], function(methodName) { var func = arrayRef[methodName]; lodash.prototype[methodName] = function() { var result = func.apply(this.__wrapped__, arguments); @@ -4351,7 +4364,7 @@ // avoid array-like object bugs with `Array#shift` and `Array#splice` // in Firefox < 10 and IE < 9 if (hasObjectSpliceBug) { - forEach(['shift', 'splice'], function(methodName) { + each(['shift', 'splice'], function(methodName) { var func = lodash.prototype[methodName]; lodash.prototype[methodName] = function() { var value = this.__wrapped__, @@ -4366,6 +4379,7 @@ } // add pseudo private property to be used and removed during the build process + lodash._each = each; lodash._iteratorTemplate = iteratorTemplate; /*--------------------------------------------------------------------------*/