Make a private each function to be used by _.forEach.

Former-commit-id: da9e22a66aef1ad9f4688f4fbb07e0806f8f0445
This commit is contained in:
John-David Dalton
2012-12-11 00:44:02 -08:00
parent 0b48b9c7d4
commit f14010a09d
2 changed files with 90 additions and 73 deletions

View File

@@ -73,7 +73,7 @@
'clone': ['assign', 'forEach', 'forOwn', 'isArray', 'isObject'], 'clone': ['assign', 'forEach', 'forOwn', 'isArray', 'isObject'],
'compact': [], 'compact': [],
'compose': [], 'compose': [],
'contains': ['forEach', 'indexOf', 'isString'], 'contains': ['indexOf', 'isString'],
'countBy': ['forEach'], 'countBy': ['forEach'],
'debounce': [], 'debounce': [],
'defaults': ['isArguments'], 'defaults': ['isArguments'],
@@ -81,12 +81,12 @@
'delay': [], 'delay': [],
'difference': ['indexOf'], 'difference': ['indexOf'],
'escape': [], 'escape': [],
'every': ['forEach', 'isArray'], 'every': ['isArray'],
'filter': ['forEach', 'isArray'], 'filter': ['isArray'],
'find': ['forEach'], 'find': ['forEach'],
'first': [], 'first': [],
'flatten': ['isArray'], 'flatten': ['isArray'],
'forEach': ['identity', 'isArguments', 'isString'], 'forEach': ['identity', 'isArguments', 'isArray', 'isString'],
'forIn': ['identity', 'isArguments'], 'forIn': ['identity', 'isArguments'],
'forOwn': ['identity', 'isArguments'], 'forOwn': ['identity', 'isArguments'],
'functions': ['forIn', 'isFunction'], 'functions': ['forIn', 'isFunction'],
@@ -95,7 +95,7 @@
'identity': [], 'identity': [],
'indexOf': ['sortedIndex'], 'indexOf': ['sortedIndex'],
'initial': [], 'initial': [],
'intersection': ['filter', 'indexOf'], 'intersection': ['forEach', 'indexOf'],
'invert': ['forOwn'], 'invert': ['forOwn'],
'invoke': ['forEach'], 'invoke': ['forEach'],
'isArguments': [], 'isArguments': [],
@@ -118,11 +118,11 @@
'keys': ['forOwn', 'isArguments', 'isObject'], 'keys': ['forOwn', 'isArguments', 'isObject'],
'last': [], 'last': [],
'lastIndexOf': [], 'lastIndexOf': [],
'map': ['forEach', 'isArray'], 'map': ['isArray'],
'max': ['forEach', 'isArray', 'isString'], 'max': ['isArray', 'isString'],
'memoize': [], 'memoize': [],
'merge': ['forOwn', 'isArray', 'isPlainObject'], 'merge': ['forOwn', 'isArray', 'isPlainObject'],
'min': ['forEach', 'isArray', 'isString'], 'min': ['isArray', 'isString'],
'mixin': ['forEach', 'forOwn', 'functions'], 'mixin': ['forEach', 'forOwn', 'functions'],
'noConflict': [], 'noConflict': [],
'object': [], 'object': [],
@@ -134,14 +134,14 @@
'pluck': ['map'], 'pluck': ['map'],
'random': [], 'random': [],
'range': [], 'range': [],
'reduce': ['forEach', 'isArray'], 'reduce': ['isArray'],
'reduceRight': ['forEach', 'isString', 'keys'], 'reduceRight': ['forEach', 'isString', 'keys'],
'reject': ['filter'], 'reject': ['filter'],
'rest': [], 'rest': [],
'result': ['isFunction'], 'result': ['isFunction'],
'shuffle': ['forEach'], 'shuffle': ['forEach'],
'size': ['keys'], 'size': ['keys'],
'some': ['forEach', 'isArray'], 'some': ['isArray'],
'sortBy': ['forEach'], 'sortBy': ['forEach'],
'sortedIndex': ['identity'], 'sortedIndex': ['identity'],
'tap': ['mixin'], 'tap': ['mixin'],
@@ -368,10 +368,10 @@
}); });
// replace wrapper `Array` method assignments // 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 [ return [
' // add `Array` mutator functions to the wrapper', ' // 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];', ' var func = arrayRef[methodName];',
' lodash.prototype[methodName] = function() {', ' lodash.prototype[methodName] = function() {',
' var value = this.__wrapped__;', ' var value = this.__wrapped__;',
@@ -387,7 +387,7 @@
' });', ' });',
'', '',
' // add `Array` accessor functions to the wrapper', ' // add `Array` accessor functions to the wrapper',
" forEach(['concat', 'join', 'slice'], function(methodName) {", " each(['concat', 'join', 'slice'], function(methodName) {",
' var func = arrayRef[methodName];', ' var func = arrayRef[methodName];',
' lodash.prototype[methodName] = function() {', ' lodash.prototype[methodName] = function() {',
' var value = this.__wrapped__,', ' var value = this.__wrapped__,',
@@ -1274,11 +1274,11 @@
dependencyMap.reduceRight = ['forEach', 'keys']; dependencyMap.reduceRight = ['forEach', 'keys'];
} }
if (isUnderscore) { if (isUnderscore) {
dependencyMap.contains = ['forEach', 'indexOf']; dependencyMap.contains = ['indexOf'];
dependencyMap.isEqual = ['isArray', 'isFunction']; dependencyMap.isEqual = ['isArray', 'isFunction'];
dependencyMap.isEmpty = ['isArray', 'isString']; dependencyMap.isEmpty = ['isArray', 'isString'];
dependencyMap.max = ['forEach', 'isArray']; dependencyMap.max = ['isArray'];
dependencyMap.min = ['forEach', 'isArray']; dependencyMap.min = ['isArray'];
dependencyMap.pick = []; dependencyMap.pick = [];
dependencyMap.template = ['defaults', 'escape']; dependencyMap.template = ['defaults', 'escape'];
@@ -1390,7 +1390,7 @@
" if (typeof length == 'number') {", " if (typeof length == 'number') {",
' result = indexOf(collection, target) > -1;', ' result = indexOf(collection, target) > -1;',
' } else {', ' } else {',
' forEach(collection, function(value) {', ' each(collection, function(value) {',
' return (result = value === target) && indicatorObject;', ' return (result = value === target) && indicatorObject;',
' });', ' });',
' }', ' }',
@@ -1734,7 +1734,8 @@
if (isMobile) { if (isMobile) {
// inline all functions defined with `createIterator` // inline all functions defined with `createIterator`
_.functions(lodash).forEach(function(methodName) { _.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)) { if (reFunc.test(source)) {
// extract, format, and inject the compiled function's source code // extract, format, and inject the compiled function's source code
source = source.replace(reFunc, function(match, captured) { source = source.replace(reFunc, function(match, captured) {
@@ -1777,13 +1778,15 @@
}); });
}()); }());
// remove chainability from `_.forEach` // remove chainability from `each` and `_.forEach`
source = source.replace(matchFunction(source, 'forEach'), function(match) { _.each(['each', 'forEach'], function(methodName) {
return match.replace(/return result([};\s]+)$/, '$1'); source = source.replace(matchFunction(source, methodName), function(match) {
return match.replace(/\n *return .+?([};\s]+)$/, '$1');
});
}); });
// unexpose "exit early" feature from `_.forEach`, `_.forIn`, and `_.forOwn` // unexpose "exit early" feature of `each`, `_.forEach`, `_.forIn`, and `_.forOwn`
_.each(['forEach', 'forIn', 'forOwn'], function(methodName) { _.each(['each', 'forEach', 'forIn', 'forOwn'], function(methodName) {
source = source.replace(matchFunction(source, methodName), function(match) { source = source.replace(matchFunction(source, methodName), function(match) {
return match.replace(/=== *false\)/g, '=== indicatorObject)'); return match.replace(/=== *false\)/g, '=== indicatorObject)');
}); });
@@ -1962,7 +1965,7 @@
// remove all `lodash.prototype` additions // remove all `lodash.prototype` additions
source = source source = source
.replace(/(?:\s*\/\/.*)*\n( *)forOwn\(lodash, *function\(func, *methodName\)[\s\S]+?\n\1}.+/g, '') .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*lodash\.prototype.+\n/g, '')
.replace(/(?:\s*\/\/.*)*\s*mixin\(lodash\).+\n/, ''); .replace(/(?:\s*\/\/.*)*\s*mixin\(lodash\).+\n/, '');
} }

112
lodash.js
View File

@@ -116,8 +116,7 @@
stringClass = '[object String]'; stringClass = '[object String]';
/** Detect various environments */ /** Detect various environments */
var isFirefox = !/1/.test(Function('1')), var isIeOpera = !!window.attachEvent,
isIeOpera = !!window.attachEvent,
isV8 = nativeBind && !/\n|true/.test(nativeBind + isIeOpera); isV8 = nativeBind && !/\n|true/.test(nativeBind + isIeOpera);
/* Detect if `Function#bind` exists and is inferred to be fast (all but V8) */ /* Detect if `Function#bind` exists and is inferred to be fast (all but V8) */
@@ -247,24 +246,25 @@
* method chaining. * method chaining.
* *
* The chainable wrapper functions are: * The chainable wrapper functions are:
* `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`, * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`, `compose`,
* `compose`, `countBy`, `debounce`, `defaults`, `defer`, `delay`, `difference`, * `concat`, `countBy`, `debounce`, `defaults`, `defer`, `delay`, `difference`,
* `filter`, `flatten`, `forEach`, `forIn`, `forOwn`, `functions`, `groupBy`, * `filter`, `flatten`, `forEach`, `forIn`, `forOwn`, `functions`, `groupBy`,
* `initial`, `intersection`, `invert`, `invoke`, `keys`, `map`, `max`, `memoize`, * `initial`, `intersection`, `invert`, `invoke`, `keys`, `map`, `max`, `memoize`,
* `merge`, `min`, `object`, `omit`, `once`, `pairs`, `partial`, `pick`, `pluck`, * `merge`, `min`, `object`, `omit`, `once`, `pairs`, `partial`, `pick`, `pluck`,
* `range`, `reject`, `rest`, `shuffle`, `sortBy`, `tap`, `throttle`, `times`, * `push`, `range`, `reject`, `rest`, `reverse`, `shuffle`, `slice`, `sort`,
* `toArray`, `union`, `uniq`, `values`, `where`, `without`, `wrap`, and `zip` * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `union`, `uniq`,
* `unshift`, `values`, `where`, `without`, `wrap`, and `zip`
* *
* The non-chainable wrapper functions are: * The non-chainable wrapper functions are:
* `clone`, `contains`, `escape`, `every`, `find`, `has`, `identity`, `indexOf`, * `clone`, `contains`, `escape`, `every`, `find`, `has`, `identity`, `indexOf`,
* `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`, `isEmpty`, * `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`, `isEmpty`,
* `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`, `isObject`, * `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`, `isObject`,
* `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `lastIndexOf`, `mixin`, * `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`, `lastIndexOf`,
* `noConflict`, `random`, `reduce`, `reduceRight`, `result`, `size`, `some`, * `mixin`, `noConflict`, `pop`, `random`, `reduce`, `reduceRight`, `result`,
* `sortedIndex`, `template`, `unescape`, and `uniqueId` * `shift`, `size`, `some`, `sortedIndex`, `template`, `unescape`, and `uniqueId`
* *
* The wrapper functions `first` and `last` return wrapped values when `n` is * The wrapper functions `first` and `last` return wrapped values when `n` is
* passed, otherwise return unwrapped values. * passed, otherwise they return unwrapped values.
* *
* @name _ * @name _
* @constructor * @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. * 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', 'args': 'collection, callback, thisArg',
'top': "callback = callback && typeof thisArg == 'undefined' ? callback : createCallback(callback, thisArg)", 'top': "callback = callback && typeof thisArg == 'undefined' ? callback : createCallback(callback, thisArg)",
'arrayLoop': 'if (callback(iteratee[index], index, collection) === false) return result', 'arrayLoop': 'if (callback(iteratee[index], index, collection) === false) return result',
@@ -696,7 +677,7 @@
data.firstArg = /^[^,]+/.exec(args)[0]; data.firstArg = /^[^,]+/.exec(args)[0];
// create the function factory // create the function factory
var factory = createFunction( var factory = Function(
'createCallback, hasOwnProperty, isArguments, isString, objectTypes, ' + 'createCallback, hasOwnProperty, isArguments, isString, objectTypes, ' +
'nativeKeys, propertyIsEnumerable', 'nativeKeys, propertyIsEnumerable',
'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' '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 * Used by `template` to escape characters for inclusion in compiled
* string literals. * string literals.
@@ -867,7 +863,7 @@
* }); * });
* // => alerts 'name' and 'bark' (order is not guaranteed) * // => alerts 'name' and 'bark' (order is not guaranteed)
*/ */
var forIn = createIterator(forEachIteratorOptions, forOwnIteratorOptions, { var forIn = createIterator(eachIteratorOptions, forOwnIteratorOptions, {
'useHas': false 'useHas': false
}); });
@@ -891,7 +887,7 @@
* }); * });
* // => alerts '0', '1', and 'length' (order is not guaranteed) * // => 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` * A fallback implementation of `isPlainObject` that checks if a given `value`
@@ -1941,7 +1937,7 @@
: indexOf(collection, target, fromIndex) : indexOf(collection, target, fromIndex)
) > -1; ) > -1;
} else { } else {
forEach(collection, function(value) { each(collection, function(value) {
if (++index >= fromIndex) { if (++index >= fromIndex) {
return !(result = value === target); return !(result = value === target);
} }
@@ -2020,7 +2016,7 @@
} }
} }
} else { } else {
forEach(collection, function(value, index, collection) { each(collection, function(value, index, collection) {
return (result = !!callback(value, index, collection)); return (result = !!callback(value, index, collection));
}); });
} }
@@ -2060,7 +2056,7 @@
} }
} }
} else { } else {
forEach(collection, function(value, index, collection) { each(collection, function(value, index, collection) {
if (callback(value, index, collection)) { if (callback(value, index, collection)) {
result.push(value); result.push(value);
} }
@@ -2124,7 +2120,24 @@
* _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, alert); * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, alert);
* // => alerts each number value (order is not guaranteed) * // => 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 * Creates an object composed of keys returned from running each element of
@@ -2228,7 +2241,7 @@
result[index] = callback(collection[index], index, collection); result[index] = callback(collection[index], index, collection);
} }
} else { } else {
forEach(collection, function(value, key, collection) { each(collection, function(value, key, collection) {
result[++index] = callback(value, key, collection); result[++index] = callback(value, key, collection);
}); });
} }
@@ -2270,7 +2283,7 @@
? charAtCallback ? charAtCallback
: createCallback(callback, thisArg); : createCallback(callback, thisArg);
forEach(collection, function(value, index, collection) { each(collection, function(value, index, collection) {
var current = callback(value, index, collection); var current = callback(value, index, collection);
if (current > computed) { if (current > computed) {
computed = current; computed = current;
@@ -2316,7 +2329,7 @@
? charAtCallback ? charAtCallback
: createCallback(callback, thisArg); : createCallback(callback, thisArg);
forEach(collection, function(value, index, collection) { each(collection, function(value, index, collection) {
var current = callback(value, index, collection); var current = callback(value, index, collection);
if (current < computed) { if (current < computed) {
computed = current; computed = current;
@@ -2393,7 +2406,7 @@
accumulator = callback(accumulator, collection[index], index, collection); accumulator = callback(accumulator, collection[index], index, collection);
} }
} else { } else {
forEach(collection, function(value, index, collection) { each(collection, function(value, index, collection) {
accumulator = noaccum accumulator = noaccum
? (noaccum = false, value) ? (noaccum = false, value)
: callback(accumulator, value, index, collection) : callback(accumulator, value, index, collection)
@@ -2550,7 +2563,7 @@
} }
} }
} else { } else {
forEach(collection, function(value, index, collection) { each(collection, function(value, index, collection) {
return !(result = callback(value, index, collection)); return !(result = callback(value, index, collection));
}); });
} }
@@ -4006,7 +4019,7 @@
: ''; : '';
try { try {
result = createFunction('_', 'return ' + source + sourceURL)(lodash); result = Function('_', 'return ' + source + sourceURL)(lodash);
} catch(e) { } catch(e) {
e.source = source; e.source = source;
throw e; throw e;
@@ -4136,7 +4149,7 @@
* // => '1,2,3' * // => '1,2,3'
*/ */
function wrapperToString() { function wrapperToString() {
return String(this.__wrapped__); return this.__wrapped__ + '';
} }
/** /**
@@ -4323,7 +4336,7 @@
lodash.prototype.valueOf = wrapperValueOf; lodash.prototype.valueOf = wrapperValueOf;
// add `Array` functions that return unwrapped values // add `Array` functions that return unwrapped values
forEach(['join', 'pop', 'shift'], function(methodName) { each(['join', 'pop', 'shift'], function(methodName) {
var func = arrayRef[methodName]; var func = arrayRef[methodName];
lodash.prototype[methodName] = function() { lodash.prototype[methodName] = function() {
return func.apply(this.__wrapped__, arguments); return func.apply(this.__wrapped__, arguments);
@@ -4331,7 +4344,7 @@
}); });
// add `Array` functions that return the wrapped value // 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]; var func = arrayRef[methodName];
lodash.prototype[methodName] = function() { lodash.prototype[methodName] = function() {
func.apply(this.__wrapped__, arguments); func.apply(this.__wrapped__, arguments);
@@ -4340,7 +4353,7 @@
}); });
// add `Array` functions that return new wrapped values // add `Array` functions that return new wrapped values
forEach(['concat', 'slice', 'splice'], function(methodName) { each(['concat', 'slice', 'splice'], function(methodName) {
var func = arrayRef[methodName]; var func = arrayRef[methodName];
lodash.prototype[methodName] = function() { lodash.prototype[methodName] = function() {
var result = func.apply(this.__wrapped__, arguments); var result = func.apply(this.__wrapped__, arguments);
@@ -4351,7 +4364,7 @@
// avoid array-like object bugs with `Array#shift` and `Array#splice` // avoid array-like object bugs with `Array#shift` and `Array#splice`
// in Firefox < 10 and IE < 9 // in Firefox < 10 and IE < 9
if (hasObjectSpliceBug) { if (hasObjectSpliceBug) {
forEach(['shift', 'splice'], function(methodName) { each(['shift', 'splice'], function(methodName) {
var func = lodash.prototype[methodName]; var func = lodash.prototype[methodName];
lodash.prototype[methodName] = function() { lodash.prototype[methodName] = function() {
var value = this.__wrapped__, var value = this.__wrapped__,
@@ -4366,6 +4379,7 @@
} }
// add pseudo private property to be used and removed during the build process // add pseudo private property to be used and removed during the build process
lodash._each = each;
lodash._iteratorTemplate = iteratorTemplate; lodash._iteratorTemplate = iteratorTemplate;
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/