diff --git a/build.js b/build.js index c9ae8105a..b4ff99098 100755 --- a/build.js +++ b/build.js @@ -97,7 +97,9 @@ 'escape': [], 'every': ['createCallback', 'isArray'], 'filter': ['createCallback', 'isArray'], - 'find': ['createCallback', 'forEach'], + 'find': ['createCallback', 'forEach', 'isArray'], + 'findIndex': [], + 'findKey': [], 'first': [], 'flatten': ['createCallback', 'isArray'], 'forEach': ['createCallback', 'isArguments', 'isArray', 'isString'], @@ -256,6 +258,8 @@ 'bindKey', 'cloneDeep', 'createCallback', + 'findIndex', + 'findKey', 'forIn', 'forOwn', 'isPlainObject', @@ -1747,6 +1751,7 @@ if (!isMobile) { dependencyMap.every = _.without(dependencyMap.every, 'isArray'); + dependencyMap.find = _.without(dependencyMap.find, 'isArray'); dependencyMap.filter = _.without(dependencyMap.filter, 'isArray'); dependencyMap.forEach = _.without(dependencyMap.forEach, 'isArray'); dependencyMap.map = _.without(dependencyMap.map, 'isArray'); @@ -1866,7 +1871,7 @@ ].join('\n')); // replace `isArray(collection)` checks in "Collections" methods with simpler type checks - _.each(['every', 'filter', 'max', 'min', 'reduce', 'some'], function(methodName) { + _.each(['every', 'filter', 'find', 'max', 'min', 'reduce', 'some'], function(methodName) { source = source.replace(matchFunction(source, methodName), function(match) { if (methodName == 'reduce') { match = match.replace(/^( *)var noaccum\b/m, '$1if (!collection) return accumulator;\n$&'); diff --git a/build/pre-compile.js b/build/pre-compile.js index f5a3aa0d6..174f154f4 100644 --- a/build/pre-compile.js +++ b/build/pre-compile.js @@ -106,6 +106,8 @@ 'fastKeys', 'filter', 'find', + 'findIndex', + 'findKey', 'first', 'flatten', 'foldl', diff --git a/lodash.js b/lodash.js index d6dee4117..e6f0a206f 100644 --- a/lodash.js +++ b/lodash.js @@ -895,6 +895,37 @@ }; } + /** + * This method is similar to `_.find`, except that it returns the key of the + * element that passes the callback check, instead of the element itself. + * + * @static + * @memberOf _ + * @alias detect + * @category Object + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the key of the found element, else `undefined`. + * @example + * + * _.findKey({ 'a': 1, 'b': 2, 'c': 3, 'd': 4 }, function(num) { return num % 2 == 0; }); + * // => 'b' + */ + function findKey(collection, callback, thisArg) { + var result; + callback = lodash.createCallback(callback, thisArg); + forOwn(collection, function(value, key, collection) { + if (callback(value, key, collection)) { + result = key; + return false; + } + }); + return result; + } + /** * Iterates over `object`'s own and inherited enumerable properties, executing * the `callback` for each property. The `callback` is bound to `thisArg` and @@ -1131,8 +1162,8 @@ * @param {Boolean} [deep=false] A flag to indicate a deep clone. * @param {Function} [callback] The function to customize cloning values. * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @param- {Array} [stackA=[]] Internally used to track traversed source objects. - * @param- {Array} [stackB=[]] Internally used to associate clones with source counterparts. + * @param- {Array} [stackA=[]] Tracks traversed source objects. + * @param- {Array} [stackB=[]] Associates clones with source counterparts. * @returns {Mixed} Returns the cloned `value`. * @example * @@ -1299,8 +1330,8 @@ * @category Objects * @param {Object} object The destination object. * @param {Object} [source1, source2, ...] The source objects. - * @param- {Object} [guard] Internally used to allow working with `_.reduce` - * without using its callback's `key` and `object` arguments as sources. + * @param- {Object} [guard] Allows working with `_.reduce` without using its + * callback's `key` and `object` arguments as sources. * @returns {Object} Returns the destination object. * @example * @@ -1485,8 +1516,8 @@ * @param {Mixed} b The other value to compare. * @param {Function} [callback] The function to customize comparing values. * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @param- {Array} [stackA=[]] Internally used track traversed `a` objects. - * @param- {Array} [stackB=[]] Internally used track traversed `b` objects. + * @param- {Array} [stackA=[]] Tracks traversed `a` objects. + * @param- {Array} [stackB=[]] Tracks traversed `b` objects. * @returns {Boolean} Returns `true`, if the values are equivalent, else `false`. * @example * @@ -1921,11 +1952,10 @@ * @param {Object} [source1, source2, ...] The source objects. * @param {Function} [callback] The function to customize merging properties. * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @param- {Object} [deepIndicator] Internally used to indicate that `stackA` - * and `stackB` are arrays of traversed objects instead of source objects. - * @param- {Array} [stackA=[]] Internally used to track traversed source objects. - * @param- {Array} [stackB=[]] Internally used to associate values with their - * source counterparts. + * @param- {Object} [deepIndicator] Indicates that `stackA` and `stackB` are + * arrays of traversed objects, instead of source objects. + * @param- {Array} [stackA=[]] Tracks traversed source objects. + * @param- {Array} [stackB=[]] Associates values with source counterparts. * @returns {Object} Returns the destination object. * @example * @@ -2466,39 +2496,49 @@ * iteration. If a property name or object is passed, it will be used to create * a "_.pluck" or "_.where" style callback, respectively. * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Mixed} Returns the element that passed the callback check, - * else `undefined`. + * @returns {Mixed} Returns the found element, else `undefined`. * @example * - * var even = _.find([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); + * _.find([1, 2, 3, 4], function(num) { return num % 2 == 0; }); * // => 2 * * var food = [ * { 'name': 'apple', 'organic': false, 'type': 'fruit' }, * { 'name': 'banana', 'organic': true, 'type': 'fruit' }, - * { 'name': 'beet', 'organic': false, 'type': 'vegetable' }, - * { 'name': 'carrot', 'organic': true, 'type': 'vegetable' } + * { 'name': 'beet', 'organic': false, 'type': 'vegetable' } * ]; * * // using "_.where" callback shorthand - * var veggie = _.find(food, { 'type': 'vegetable' }); + * _.find(food, { 'type': 'vegetable' }); * // => { 'name': 'beet', 'organic': false, 'type': 'vegetable' } * * // using "_.pluck" callback shorthand - * var healthy = _.find(food, 'organic'); + * _.find(food, 'organic'); * // => { 'name': 'banana', 'organic': true, 'type': 'fruit' } */ function find(collection, callback, thisArg) { - var result; callback = lodash.createCallback(callback, thisArg); - forEach(collection, function(value, index, collection) { - if (callback(value, index, collection)) { - result = value; - return false; + if (isArray(collection)) { + var index = -1, + length = collection.length; + + while (++index < length) { + var value = collection[index]; + if (callback(value, index, collection)) { + return value; + } } - }); - return result; + } else { + var result; + each(collection, function(value, index, collection) { + if (callback(value, index, collection)) { + result = value; + return false; + } + }); + return result; + } } /** @@ -3257,6 +3297,38 @@ return result; } + /** + * This method is similar to `_.find`, except that it returns the index of + * the element that passes the callback check, instead of the element itself. + * + * @static + * @memberOf _ + * @alias detect + * @category Arrays + * @param {Array|Object|String} collection The collection to iterate over. + * @param {Function|Object|String} [callback=identity] The function called per + * iteration. If a property name or object is passed, it will be used to create + * a "_.pluck" or "_.where" style callback, respectively. + * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @returns {Mixed} Returns the index of the found element, else `-1`. + * @example + * + * _.findIndex(['apple', 'banana', 'beet'], function(food) { return /^b/.test(food); }); + * // => 1 + */ + function findIndex(collection, callback, thisArg) { + var index = -1, + length = collection ? collection.length : 0; + + callback = lodash.createCallback(callback, thisArg); + while (++index < length) { + if (callback(collection[index], index, collection)) { + return index; + } + } + return -1; + } + /** * Gets the first element of the `array`. If a number `n` is passed, the first * `n` elements of the `array` are returned. If a `callback` function is passed, @@ -4244,8 +4316,7 @@ * If `func` is an object, the created callback will return `true` for elements * that contain the equivalent object properties, otherwise it will return `false`. * - * Note: All Lo-Dash methods, that accept a `callback` argument, internally - * use `_.createCallback`. + * Note: All Lo-Dash methods, that accept a `callback` argument, use `_.createCallback`. * * @static * @memberOf _ @@ -5199,6 +5270,8 @@ lodash.escape = escape; lodash.every = every; lodash.find = find; + lodash.findIndex = findIndex; + lodash.findKey = findKey; lodash.has = has; lodash.identity = identity; lodash.indexOf = indexOf; diff --git a/test/test-build.js b/test/test-build.js index 8db52ca52..6f4eb9187 100644 --- a/test/test-build.js +++ b/test/test-build.js @@ -97,6 +97,7 @@ 'compact', 'difference', 'drop', + 'findIndex', 'first', 'flatten', 'head', @@ -187,6 +188,7 @@ 'cloneDeep', 'defaults', 'extend', + 'findKey', 'forIn', 'forOwn', 'functions', @@ -293,6 +295,8 @@ 'bindKey', 'cloneDeep', 'createCallback', + 'findIndex', + 'findKey', 'forIn', 'forOwn', 'isPlainObject', @@ -946,6 +950,8 @@ 'at', 'bindKey', 'createCallback', + 'findIndex', + 'findKey', 'forIn', 'forOwn', 'isPlainObject', @@ -1362,6 +1368,9 @@ isUnderscore = /backbone|underscore/.test(command), exposeAssign = !isUnderscore, exposeCreateCallback = !isUnderscore, + exposeForIn = !isUnderscore, + exposeForOwn = !isUnderscore, + exposeIsPlainObject = !isUnderscore, exposeZipObject = !isUnderscore; try { @@ -1418,12 +1427,24 @@ // remove nonexistent and duplicate method names methodNames = _.uniq(_.intersection(allMethods, expandMethodNames(methodNames))); + if (isUnderscore) { + methodNames = _.without.apply(_, [methodNames].concat(['findIndex', 'findKey'])); + } if (!exposeAssign) { methodNames = _.without(methodNames, 'assign'); } if (!exposeCreateCallback) { methodNames = _.without(methodNames, 'createCallback'); } + if (!exposeForIn) { + methodNames = _.without(methodNames, 'forIn'); + } + if (!exposeForOwn) { + methodNames = _.without(methodNames, 'forOwn'); + } + if (!exposeIsPlainObject) { + methodNames = _.without(methodNames, 'isPlainobject'); + } if (!exposeZipObject) { methodNames = _.without(methodNames, 'zipObject'); } diff --git a/test/test.js b/test/test.js index 423da08b3..026a1150a 100644 --- a/test/test.js +++ b/test/test.js @@ -671,8 +671,6 @@ /*--------------------------------------------------------------------------*/ - QUnit.module('lodash.find'); - (function() { var objects = [ { 'a': 0, 'b': 0 }, @@ -680,20 +678,31 @@ { 'a': 2, 'b': 2 } ]; - test('should return found `value`', function() { - equal(_.find(objects, function(object) { return object.a == 1; }), objects[1]); - }); + _.each({ + 'find': [objects[1], undefined, objects[2], objects[1]], + 'findIndex': [1, -1, 2, 1], + 'findKey': ['1', undefined, '2', '1'] + }, + function(expected, methodName) { + QUnit.module('lodash.' + methodName); - test('should return `undefined` if `value` is not found', function() { - equal(_.find(objects, function(object) { return object.a == 3; }), undefined); - }); + var func = _[methodName]; - test('should work with an object for `callback`', function() { - equal(_.find(objects, { 'b': 2 }), objects[2]); - }); + test('should return the correct value', function() { + strictEqual(func(objects, function(object) { return object.a == 1; }), expected[0]); + }); - test('should work with a string for `callback`', function() { - equal(_.find(objects, 'b'), objects[1]); + test('should return `' + expected[1] + '` if value is not found', function() { + strictEqual(func(objects, function(object) { return object.a == 3; }), expected[1]); + }); + + test('should work with an object for `callback`', function() { + strictEqual(func(objects, { 'b': 2 }), expected[2]); + }); + + test('should work with a string for `callback`', function() { + strictEqual(func(objects, 'b'), expected[3]); + }); }); }());