From 24c9b6e211fa44ee5b4ffb6563c43b698dc7ac92 Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Thu, 14 Jun 2012 00:28:36 -0400 Subject: [PATCH] Move `_.pluck` and `_.invoke` back to the "Collections" category and optimize `_.sortedIndex` when a `callback` is passed. Former-commit-id: d16763e7d35660d8ba9ea976c8b2a4dc20f1211f --- build/pre-compile.js | 6 +- lodash.js | 169 +++++++++++++++++++++---------------------- test/test.js | 26 ++++++- 3 files changed, 112 insertions(+), 89 deletions(-) diff --git a/build/pre-compile.js b/build/pre-compile.js index d3d67945c..7f0a30502 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -8,6 +8,7 @@ /** Used to minify variables embedded in compiled strings */ var compiledVars = [ 'accumulator', + 'args', 'arrayClass', 'callback', 'className', @@ -18,14 +19,17 @@ 'hasOwnProperty', 'identity', 'index', + 'isFunc', 'iteratorBind', 'length', + 'methodName', + 'noaccum', 'object', 'objectTypes', - 'noaccum', 'property', 'result', 'skipProto', + 'slice', 'source', 'sourceIndex', 'stringClass', diff --git a/lodash.js b/lodash.js index f06744ae3..0020ad663 100644 --- a/lodash.js +++ b/lodash.js @@ -339,6 +339,20 @@ } }; + /** Reusable iterator options for `invoke`, `map`, and `pluck` */ + var mapIteratorOptions = { + 'init': '', + 'exit': 'if (!collection) return []', + 'beforeLoop': { + 'array': 'result = Array(length)', + 'object': 'result = []' + }, + 'inLoop': { + 'array': 'result[index] = callback(collection[index], index, collection)', + 'object': 'result.push(callback(collection[index], index, collection))' + } + }; + /*--------------------------------------------------------------------------*/ /** @@ -437,13 +451,13 @@ // create the function factory var factory = Function( 'arrayClass, funcClass, hasOwnProperty, identity, iteratorBind, objectTypes, ' + - 'stringClass, toString, undefined', + 'slice, stringClass, toString, undefined', '"use strict"; return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' ); // return the compiled function return factory( arrayClass, funcClass, hasOwnProperty, identity, iteratorBind, objectTypes, - stringClass, toString + slice, stringClass, toString ); } @@ -679,7 +693,40 @@ var forEach = createIterator(baseIteratorOptions, forEachIteratorOptions); /** - * Produces a new array of values by mapping each value in the `collection` + * Invokes the method named by `methodName` on each element in the `collection`. + * Additional arguments will be passed to each invoked method. If `methodName` + * is a function it will be invoked for, and `this` bound to, each element + * in the `collection`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object} 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. + * @returns {Array} Returns a new array of values returned from each invoked method. + * @example + * + * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort'); + * // => [[1, 5, 7], [1, 2, 3]] + * + * _.invoke([123, 456], String.prototype.split, ''); + * // => [['1', '2', '3'], ['4', '5', '6']] + */ + var invoke = createIterator(mapIteratorOptions, { + 'args': 'collection, methodName', + 'top': + '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.push((isFunc ? methodName : collection[index][methodName]).apply(collection[index], args))' + } + }); + + /** + * Produces a new array of values by mapping each element in the `collection` * through a transformation `callback`. The `callback` is bound to `thisArg` * and invoked with 3 arguments; for arrays they are (value, index, array) * and for objects they are (value, key, object). @@ -700,16 +747,34 @@ * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; }); * // => [3, 6, 9] (order is not guaranteed) */ - var map = createIterator(baseIteratorOptions, { - 'init': '', - 'exit': 'if (!collection) return []', - 'beforeLoop': { - 'array': 'result = Array(length)', - 'object': 'result = []' - }, + var map = createIterator(baseIteratorOptions, mapIteratorOptions); + + /** + * Retrieves the value of a specified property from all elements in + * the `collection`. + * + * @static + * @memberOf _ + * @category Collections + * @param {Array|Object} collection The collection to iterate over. + * @param {String} property The property to pluck. + * @returns {Array} Returns a new array of property values. + * @example + * + * var stooges = [ + * { 'name': 'moe', 'age': 40 }, + * { 'name': 'larry', 'age': 50 }, + * { 'name': 'curly', 'age': 60 } + * ]; + * + * _.pluck(stooges, 'name'); + * // => ['moe', 'larry', 'curly'] + */ + var pluck = createIterator(mapIteratorOptions, { + 'args': 'collection, property', 'inLoop': { - 'array': 'result[index] = callback(collection[index], index, collection)', - 'object': 'result.push(callback(collection[index], index, collection))' + 'array': 'result[index] = collection[index][property]', + 'object': 'result.push(collection[index][property])' } }); @@ -1159,44 +1224,6 @@ return result; } - /** - * Invokes the method named by `methodName` on each element of `array`. - * Additional arguments will be passed to each invoked method. If `methodName` - * is a function it will be invoked for, and `this` bound to, each element - * of `array`. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} array The array 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. - * @returns {Array} Returns a new array of values returned from each invoked method. - * @example - * - * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort'); - * // => [[1, 5, 7], [1, 2, 3]] - * - * _.invoke([123, 456], String.prototype.split, ''); - * // => [['1', '2', '3'], ['4', '5', '6']] - */ - function invoke(array, methodName) { - var result = []; - if (!array) { - return result; - } - var args = slice.call(arguments, 2), - index = -1, - length = array.length, - isFunc = typeof methodName == 'function'; - - while (++index < length) { - result[index] = (isFunc ? methodName : array[index][methodName]).apply(array[index], args); - } - return result; - } - /** * Gets the last value of the `array`. Pass `n` to return the lasy `n` values * of the `array`. @@ -1363,40 +1390,6 @@ return result; } - /** - * Retrieves the value of a specified property from all elements in `array`. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} array The array to iterate over. - * @param {String} property The property to pluck. - * @returns {Array} Returns a new array of property values. - * @example - * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 }, - * { 'name': 'curly', 'age': 60 } - * ]; - * - * _.pluck(stooges, 'name'); - * // => ['moe', 'larry', 'curly'] - */ - function pluck(array, property) { - if (!array) { - return []; - } - var index = -1, - length = array.length, - result = Array(length); - - while (++index < length) { - result[index] = array[index][property]; - } - return result; - } - /** * Creates an array of numbers (positive and/or negative) progressing from * `start` up to but not including `stop`. This method is a port of Python's @@ -1608,10 +1601,14 @@ high = array.length; if (callback) { - value = callback.call(thisArg, value); + if (thisArg) { + var fn = callback; + callback = function(value) { return fn.call(thisArg, value); }; + } + value = callback(value); while (low < high) { mid = (low + high) >>> 1; - callback.call(thisArg, array[mid]) < value ? low = mid + 1 : high = mid; + callback(array[mid]) < value ? low = mid + 1 : high = mid; } } else { while (low < high) { diff --git a/test/test.js b/test/test.js index 78711a495..f879363bc 100644 --- a/test/test.js +++ b/test/test.js @@ -378,6 +378,17 @@ /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.invoke'); + + (function() { + test('should work with an object for `collection`', function() { + var object = { 'a': 1, 'b': 2, 'c': 3 }; + deepEqual(_.invoke(object, 'toFixed', 1), ['1.0', '2.0', '3.0']); + }); + }()); + + /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.isEmpty'); (function() { @@ -520,6 +531,17 @@ /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.pluck'); + + (function() { + test('should work with an object for `collection`', function() { + var object = { 'a': [1], 'b': [1, 2], 'c': [1, 2, 3] }; + deepEqual(_.pluck(object, 'length'), [1, 2, 3]); + }); + }()); + + /*--------------------------------------------------------------------------*/ + QUnit.module('lodash.reduceRight'); (function() { @@ -743,12 +765,10 @@ 'indexOf', 'initial', 'intersection', - 'invoke', 'last', 'lastIndexOf', 'max', 'min', - 'pluck', 'range', 'rest', 'shuffle', @@ -782,7 +802,9 @@ 'filter', 'find', 'forEach', + 'invoke', 'map', + 'pluck', 'reduce', 'reduceRight', 'reject',