diff --git a/lodash.js b/lodash.js index 06439162d..56879c4eb 100644 --- a/lodash.js +++ b/lodash.js @@ -715,6 +715,23 @@ }; }()); + /** + * Checks if the provided arguments are from an iteratee call. + * + * @private + * @param {*} value The potential iteratee value argument. + * @param {*} index The potential iteratee index or key argument. + * @param {*} object The potential iteratee object argument. + * @returns {boolean} Returns `true` if the arguments are from an iteratee call, else `false`. + */ + function isIterateeCall(value, index, object) { + var indexType = typeof index, + objectType = typeof object; + + return (object && (indexType == 'number' || indexType == 'string') && + (objectType == 'function' || objectType == 'object') && object[index] === value) || false; + } + /** * Used by `_.trimmedLeftIndex` and `_.trimmedRightIndex` to determine if a * character code is whitespace. @@ -2521,12 +2538,10 @@ var length = arguments.length, object = arguments[0]; - if (object == null || length < 2) { + if (length < 2 || object == null) { return object; } - // enables use as a callback for functions like `_.reduce` - var type = typeof arguments[2]; - if ((type == 'number' || type == 'string') && arguments[3] && arguments[3][arguments[2]] === arguments[1]) { + if (isIterateeCall(arguments[1], arguments[2], arguments[3])) { length = 2; } // juggle arguments @@ -3190,6 +3205,7 @@ * @category Array * @param {Array} array The array to process. * @param {numer} [size=1] The length of each chunk. + * @param- {Object} [guard] Enables use as a callback for functions like `_.map`. * @returns {Array} Returns the new array containing chunks. * @example * @@ -3199,13 +3215,13 @@ * _.chunk(['a', 'b', 'c', 'd'], 3); * // => [['a', 'b', 'c'], ['d']] */ - function chunk(array, size) { + function chunk(array, size, guard) { var index = 0, length = array ? array.length : 0, resIndex = -1, result = []; - size = typeof size == 'undefined' ? 1 : nativeMax(+size || 1, 1); + size = (guard || size == null) ? 1 : nativeMax(+size || 1, 1); while (index < length) { result[++resIndex] = slice(array, index, (index += size)); } @@ -3299,7 +3315,7 @@ * // => [1, 2, 3] */ function drop(array, n, guard) { - n = (n == null || guard) ? 1 : n; + n = (guard || n == null) ? 1 : n; return slice(array, n < 0 ? 0 : n); } @@ -3330,7 +3346,7 @@ */ function dropRight(array, n, guard) { var length = array ? array.length : 0; - n = (n == null || guard) ? 1 : n; + n = (guard || n == null) ? 1 : n; n = length - (n || 0); return slice(array, 0, n < 0 ? 0 : n); } @@ -3583,15 +3599,7 @@ */ function flatten(array, isDeep, guard) { var length = array ? array.length : 0; - if (!length) { - return []; - } - // enables use as a callback for functions like `_.map` - var type = typeof isDeep; - if ((type == 'number' || type == 'string') && guard && guard[isDeep] === array) { - isDeep = false; - } - return baseFlatten(array, isDeep); + return length ? baseFlatten(array, guard ? false : isDeep) : []; } /** @@ -3953,6 +3961,10 @@ var index = -1, length = array ? array.length : 0; + if (isIterateeCall(array, start, end)) { + start = 0; + end = length; + } start = start == null ? 0 : (+start || 0); if (start < 0) { start = -start > length ? 0 : (length + start); @@ -4075,7 +4087,7 @@ * // => [] */ function take(array, n, guard) { - n = (n == null || guard) ? 1 : n; + n = (guard || n == null) ? 1 : n; return slice(array, 0, n < 0 ? 0 : n); } @@ -4106,7 +4118,7 @@ */ function takeRight(array, n, guard) { var length = array ? array.length : 0; - n = (n == null || guard) ? 1 : n; + n = (guard || n == null) ? 1 : n; n = length - (n || 0); return slice(array, n < 0 ? 0 : n); } @@ -4284,16 +4296,10 @@ return []; } // juggle arguments - var type = typeof isSorted; - if (type != 'boolean' && isSorted != null) { + if (typeof isSorted != 'boolean' && isSorted != null) { thisArg = iteratee; - iteratee = isSorted; + iteratee = isIterateeCall(array, isSorted, thisArg) ? null : isSorted; isSorted = false; - - // enables use as a callback for functions like `_.map` - if ((type == 'number' || type == 'string') && thisArg && thisArg[iteratee] === array) { - iteratee = null; - } } if (iteratee != null) { iteratee = getCallback(iteratee, thisArg, 3); @@ -5177,17 +5183,14 @@ * // => { 'user': 'fred', 'age': 40 }; */ function max(collection, iteratee, thisArg) { - var computed = -Infinity, - result = computed, - type = typeof iteratee; - - // enables use as a callback for functions like `_.map` - if ((type == 'number' || type == 'string') && thisArg && thisArg[iteratee] === collection) { + if (isIterateeCall(collection, iteratee, thisArg)) { iteratee = null; } - var noIteratee = iteratee == null, + var computed = -Infinity, + noIteratee = iteratee == null, isArr = noIteratee && isArray(collection), - isStr = !isArr && isString(collection); + isStr = !isArr && isString(collection), + result = computed; if (noIteratee && !isStr) { var index = -1, @@ -5260,17 +5263,14 @@ * // => { 'user': 'barney', 'age': 36 }; */ function min(collection, iteratee, thisArg) { - var computed = Infinity, - result = computed, - type = typeof iteratee; - - // enables use as a callback for functions like `_.map` - if ((type == 'number' || type == 'string') && thisArg && thisArg[iteratee] === collection) { + if (isIterateeCall(collection, iteratee, thisArg)) { iteratee = null; } - var noIteratee = iteratee == null, + var computed = Infinity, + noIteratee = iteratee == null, isArr = noIteratee && isArray(collection), - isStr = !isArr && isString(collection); + isStr = !isArr && isString(collection), + result = computed; if (noIteratee && !isStr) { var index = -1, @@ -5497,7 +5497,7 @@ * // => [3, 1] */ function sample(collection, n, guard) { - if (n == null || guard) { + if (guard || n == null) { collection = toIterable(collection); var length = collection.length; return length > 0 ? collection[baseRandom(0, length - 1)] : undefined; @@ -5666,6 +5666,9 @@ * // = > [['barney', 26], ['barney', 36], ['fred', 30], ['fred', 40]] */ function sortBy(collection, iteratee, thisArg) { + if (isIterateeCall(collection, iteratee, thisArg)) { + iteratee = null; + } var index = -1, length = collection ? collection.length : 0, multi = iteratee && isArray(iteratee), @@ -5962,6 +5965,7 @@ * @category Function * @param {Function} func The function to curry. * @param {number} [arity=func.length] The arity of `func`. + * @param- {Object} [guard] Enables use as a callback for functions like `_.map`. * @returns {Function} Returns the new curried function. * @example * @@ -5978,8 +5982,8 @@ * curried(1, 2, 3); * // => [1, 2, 3] */ - function curry(func, arity) { - var result = baseCurry(func, CURRY_FLAG, arity); + function curry(func, arity, guard) { + var result = baseCurry(func, CURRY_FLAG, guard ? null : arity); result.placeholder = curry.placeholder; return result; } @@ -5995,6 +5999,7 @@ * @category Function * @param {Function} func The function to curry. * @param {number} [arity=func.length] The arity of `func`. + * @param- {Object} [guard] Enables use as a callback for functions like `_.map`. * @returns {Function} Returns the new curried function. * @example * @@ -6011,8 +6016,8 @@ * curried(1, 2, 3); * // => [1, 2, 3] */ - function curryRight(func, arity) { - var result = baseCurry(func, CURRY_RIGHT_FLAG, arity); + function curryRight(func, arity, guard) { + var result = baseCurry(func, CURRY_RIGHT_FLAG, guard ? null : arity); result.placeholder = curryRight.placeholder; return result; } @@ -6628,18 +6633,11 @@ * // => 0 */ function clone(value, isDeep, customizer, thisArg) { - var type = typeof isDeep; - // juggle arguments - if (type != 'boolean' && isDeep != null) { + if (typeof isDeep != 'boolean' && isDeep != null) { thisArg = customizer; - customizer = isDeep; + customizer = isIterateeCall(value, isDeep, thisArg) ? null : isDeep; isDeep = false; - - // enables use as a callback for functions like `_.map` - if ((type == 'number' || type == 'string') && thisArg && thisArg[customizer] === value) { - customizer = null; - } } customizer = typeof customizer == 'function' && baseCallback(customizer, thisArg, 1); return baseClone(value, isDeep, customizer); @@ -7259,6 +7257,7 @@ * @category Object * @param {Object} prototype The object to inherit from. * @param {Object} [properties] The properties to assign to the object. + * @param- {Object} [guard] Enables use as a callback for functions like `_.map`. * @returns {Object} Returns the new object. * @example * @@ -7280,8 +7279,9 @@ * circle instanceof Shape; * // => true */ - function create(prototype, properties) { + function create(prototype, properties, guard) { var result = baseCreate(prototype); + properties = guard ? null : properties; return properties ? baseAssign(result, properties) : result; } @@ -7568,6 +7568,7 @@ * @category Object * @param {Object} object The object to invert. * @param {boolean} [multiValue=false] Allow multiple values per key. + * @param- {Object} [guard] Enables use as a callback for functions like `_.map`. * @returns {Object} Returns the new inverted object. * @example * @@ -7582,7 +7583,9 @@ * _.invert({ 'first': 'fred', 'second': 'barney', 'third': 'fred' }, true); * // => { 'fred': ['first', 'third'], 'barney': ['second'] } */ - function invert(object, multiValue) { + function invert(object, multiValue, guard) { + multiValue = guard ? null : multiValue; + var index = -1, props = keys(object), length = props.length, @@ -8472,6 +8475,9 @@ // http://ejohn.org/blog/javascript-micro-templating/ // and Laura Doktorova's doT.js // https://github.com/olado/doT + if (isIterateeCall(string, options, otherOptions)) { + options = otherOptions = null; + } var settings = lodash.templateSettings; options = assign({}, otherOptions || options, settings, assignOwnDefaults); string = String(string == null ? '' : string); @@ -8577,6 +8583,7 @@ * @category String * @param {string} [string=''] The string to trim. * @param {string} [chars=whitespace] The characters to trim. + * @param- {Object} [guard] Enables use as a callback for functions like `_.map`. * @returns {string} Returns the trimmed string. * @example * @@ -8586,12 +8593,12 @@ * _.trim('-_-fred-_-', '_-'); * // => 'fred' */ - function trim(string, chars) { + function trim(string, chars, guard) { string = string == null ? '' : String(string); if (!string) { return string; } - if (chars == null) { + if (guard || chars == null) { return string.slice(trimmedLeftIndex(string), trimmedRightIndex(string) + 1); } chars = String(chars); @@ -8606,6 +8613,7 @@ * @category String * @param {string} [string=''] The string to trim. * @param {string} [chars=whitespace] The characters to trim. + * @param- {Object} [guard] Enables use as a callback for functions like `_.map`. * @returns {string} Returns the trimmed string. * @example * @@ -8615,12 +8623,12 @@ * _.trimLeft('-_-fred-_-', '_-'); * // => 'fred-_-' */ - function trimLeft(string, chars) { + function trimLeft(string, chars, guards) { string = string == null ? '' : String(string); if (!string) { return string; } - if (chars == null) { + if (guards || chars == null) { return string.slice(trimmedLeftIndex(string)) } chars = String(chars); @@ -8635,6 +8643,7 @@ * @category String * @param {string} [string=''] The string to trim. * @param {string} [chars=whitespace] The characters to trim. + * @param- {Object} [guard] Enables use as a callback for functions like `_.map`. * @returns {string} Returns the trimmed string. * @example * @@ -8644,12 +8653,12 @@ * _.trimRight('-_-fred-_-', '_-'); * // => '-_-fred' */ - function trimRight(string, chars) { + function trimRight(string, chars, guard) { string = string == null ? '' : String(string); if (!string) { return string; } - if (chars == null) { + if (guard || chars == null) { return string.slice(0, trimmedRightIndex(string) + 1) } chars = String(chars); @@ -8669,6 +8678,7 @@ * @param {number} [options.length=30] The maximum string length. * @param {string} [options.omission='...'] The string to indicate text is omitted. * @param {RegExp|string} [options.separator] The separator pattern to truncate to. + * @param- {Object} [guard] Enables use as a callback for functions like `_.map`. * @returns {string} Returns the truncated string. * @example * @@ -8828,6 +8838,7 @@ * @category Utility * @param {*} [func=identity] The value to convert to a callback. * @param {*} [thisArg] The `this` binding of the created callback. + * @param- {Object} [guard] Enables use as a callback for functions like `_.map`. * @returns {Function} Returns the new function. * @example * @@ -8850,8 +8861,8 @@ * _.filter(users, 'age__gt38'); * // => [{ 'user': 'fred', 'age': 40 }] */ - function callback(func, thisArg) { - return baseCallback(func, thisArg); + function callback(func, thisArg, guard) { + return baseCallback(func, guard ? undefined : thisArg); } /** @@ -9185,9 +9196,7 @@ * // => a floating-point number between 1.2 and 5.2 */ function random(min, max, floating) { - // enables use as a callback for functions like `_.map` - var type = typeof max; - if ((type == 'number' || type == 'string') && floating && floating[max] === min) { + if (isIterateeCall(min, max, floating)) { max = floating = null; } var noMin = min == null, @@ -9254,13 +9263,10 @@ * // => [] */ function range(start, end, step) { - start = +start || 0; - - // enables use as a callback for functions like `_.map` - var type = typeof end; - if ((type == 'number' || type == 'string') && step && step[end] === start) { + if (isIterateeCall(start, end, step)) { end = step = null; } + start = +start || 0; step = step == null ? 1 : (+step || 0); if (end == null) {