Add _.forOwn and _.forIn.

Former-commit-id: f4e94a0fd15318063eec16c464435b07f419fa6a
This commit is contained in:
John-David Dalton
2012-06-04 14:39:36 -04:00
parent b484d4b2dc
commit 74649b5f28
4 changed files with 177 additions and 60 deletions

View File

@@ -79,6 +79,8 @@
'first': [], 'first': [],
'flatten': ['isArray'], 'flatten': ['isArray'],
'forEach': ['createIterator'], 'forEach': ['createIterator'],
'forIn': ['createIterator'],
'forOwn': ['createIterator'],
'functions': ['createIterator'], 'functions': ['createIterator'],
'groupBy': ['createIterator'], 'groupBy': ['createIterator'],
'has': [], 'has': [],

View File

@@ -101,6 +101,8 @@
'foldl', 'foldl',
'foldr', 'foldr',
'forEach', 'forEach',
'forIn',
'forOwn',
'functions', 'functions',
'groupBy', 'groupBy',
'has', 'has',

157
lodash.js
View File

@@ -323,6 +323,13 @@
'top': 'if (thisArg) callback = iteratorBind(callback, thisArg)' 'top': 'if (thisArg) callback = iteratorBind(callback, thisArg)'
}; };
/** Reusable iterator options for `forIn` and `forOwn` */
var forOwnIteratorOptions = {
'inLoop': {
'object': baseIteratorOptions.inLoop
}
};
/** Reusable iterator options for `map`, `pluck`, and `values` */ /** Reusable iterator options for `map`, `pluck`, and `values` */
var mapIteratorOptions = { var mapIteratorOptions = {
'init': '', 'init': '',
@@ -587,8 +594,9 @@
/** /**
* Checks if the `callback` returns a truthy value for **all** elements of a * Checks if the `callback` returns a truthy value for **all** elements of a
* `collection`. The `callback` is invoked with 3 arguments; for arrays they * `collection`. The `callback` is bound to `thisArg` and invoked with 3
* are (value, index, array) and for objects they are (value, key, object). * arguments; for arrays they are (value, index, array) and for objects they
* are (value, key, object).
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -607,9 +615,9 @@
/** /**
* Examines each value in a `collection`, returning an array of all values the * Examines each value in a `collection`, returning an array of all values the
* `callback` returns truthy for. The `callback` is invoked with 3 arguments; * `callback` returns truthy for. The `callback` is bound to `thisArg` and
* for arrays they are (value, index, array) and for objects they are * invoked with 3 arguments; for arrays they are (value, index, array) and for
* (value, key, object). * objects they are (value, key, object).
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -630,8 +638,8 @@
* Examines each value in a `collection`, returning the first one the `callback` * Examines each value in a `collection`, returning the first one the `callback`
* returns truthy for. The function returns as soon as it finds an acceptable * returns truthy for. The function returns as soon as it finds an acceptable
* value, and does not iterate over the entire `collection`. The `callback` is * value, and does not iterate over the entire `collection`. The `callback` is
* invoked with 3 arguments; for arrays they are (value, index, array) and for * bound to `thisArg` and invoked with 3 arguments; for arrays they are
* objects they are (value, key, object). * (value, index, array) and for objects they are (value, key, object).
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -653,9 +661,9 @@
/** /**
* Iterates over a `collection`, executing the `callback` for each value in the * Iterates over a `collection`, executing the `callback` for each value in the
* `collection`. The `callback` is bound to the `thisArg` value, if one is passed. * `collection`. The `callback` is bound to `thisArg` and invoked with 3
* The `callback` is invoked with 3 arguments; for arrays they are * arguments; for arrays they are (value, index, array) and for objects they
* (value, index, array) and for objects they are (value, key, object). * are (value, key, object).
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -667,19 +675,19 @@
* @returns {Array|Object} Returns the `collection`. * @returns {Array|Object} Returns the `collection`.
* @example * @example
* *
* _.forEach({ 'one': 1, 'two': 2, 'three': 3}, function(num) { alert(num); });
* // => alerts each number in turn
*
* _([1, 2, 3]).forEach(function(num) { alert(num); }).join(','); * _([1, 2, 3]).forEach(function(num) { alert(num); }).join(',');
* // => alerts each number in turn and returns '1,2,3' * // => alerts each number and returns '1,2,3'
*
* _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { alert(num); });
* // => alerts each number (order is not guaranteed)
*/ */
var forEach = createIterator(baseIteratorOptions, forEachIteratorOptions); var forEach = createIterator(baseIteratorOptions, forEachIteratorOptions);
/** /**
* Produces a new array of values by mapping each value in the `collection` * Produces a new array of values by mapping each value in the `collection`
* through a transformation `callback`. The `callback` is bound to the `thisArg` * through a transformation `callback`. The `callback` is bound to `thisArg`
* value, if one is passed. The `callback` is invoked with 3 arguments; for * and invoked with 3 arguments; for arrays they are (value, index, array)
* arrays they are (value, index, array) and for objects they are (value, key, object). * and for objects they are (value, key, object).
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -695,7 +703,7 @@
* // => [3, 6, 9] * // => [3, 6, 9]
* *
* _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; }); * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; });
* // => [3, 6, 9] * // => [3, 6, 9] (order is not guaranteed)
*/ */
var map = createIterator(baseIteratorOptions, mapIteratorOptions); var map = createIterator(baseIteratorOptions, mapIteratorOptions);
@@ -730,10 +738,9 @@
/** /**
* Boils down a `collection` to a single value. The initial state of the * Boils down a `collection` to a single value. The initial state of the
* reduction is `accumulator` and each successive step of it should be returned * reduction is `accumulator` and each successive step of it should be returned
* by the `callback`. The `callback` is bound to the `thisArg` value, if one is * by the `callback`. The `callback` is bound to `thisArg` and invoked with 4
* passed. The `callback` is invoked with 4 arguments; for arrays they are * arguments; for arrays they are (accumulator, value, index, array) and for
* (accumulator, value, index, array) and for objects they are * objects they are (accumulator, value, key, object).
* (accumulator, value, key, object).
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -769,10 +776,7 @@
}); });
/** /**
* The right-associative version of `_.reduce`. The `callback` is bound to the * The right-associative version of `_.reduce`.
* `thisArg` value, if one is passed. The `callback` is invoked with 4 arguments;
* for arrays they are (accumulator, value, index, array) and for objects they
* are (accumulator, value, key, object).
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -826,9 +830,7 @@
/** /**
* The opposite of `_.filter`, this method returns the values of a `collection` * The opposite of `_.filter`, this method returns the values of a `collection`
* that `callback` does **not** return truthy for. The `callback` is invoked * that `callback` does **not** return truthy for.
* with 3 arguments; for arrays they are (value, index, array) and for objects
* they are (value, key, object).
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -849,9 +851,9 @@
/** /**
* Checks if the `callback` returns a truthy value for **any** element of a * Checks if the `callback` returns a truthy value for **any** element of a
* `collection`. The function returns as soon as it finds passing value, and * `collection`. The function returns as soon as it finds passing value, and
* does not iterate over the entire `collection`. The `callback` is invoked * does not iterate over the entire `collection`. The `callback` is bound to
* with 3 arguments; for arrays they are (value, index, array) and for objects * `thisArg` and invoked with 3 arguments; for arrays they are
* they are (value, key, object). * (value, index, array) and for objects they are (value, key, object).
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -1040,9 +1042,9 @@
/** /**
* Splits a `collection` into sets, grouped by the result of running each value * Splits a `collection` into sets, grouped by the result of running each value
* through `callback`. The `callback` is invoked with 3 arguments; * through `callback`. The `callback` is bound to `thisArg` and invoked with 3
* (value, index, array). The `callback` argument may also be the name of a * arguments; (value, index, array). The `callback` argument may also be the
* property to group by. * name of a property to group by.
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -1085,9 +1087,8 @@
/** /**
* Produces a new sorted array, ranked in ascending order by the results of * Produces a new sorted array, ranked in ascending order by the results of
* running each value of a `collection` through `callback`. The `callback` is * running each value of a `collection` through `callback`. The `callback` is
* invoked with 3 arguments; for arrays they are (value, index, array) and for * bound to `thisArg` and invoked with 3 arguments; (value, index, array). The
* objects they are (value, key, object). The `callback` argument may also be * `callback` argument may also be the name of a property to sort by (e.g. 'length').
* the name of a property to sort by (e.g. 'length').
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -1316,8 +1317,8 @@
/** /**
* Retrieves the maximum value of an `array`. If `callback` is passed, * Retrieves the maximum value of an `array`. If `callback` is passed,
* it will be executed for each value in the `array` to generate the * it will be executed for each value in the `array` to generate the
* criterion by which the value is ranked. The `callback` is invoked with 3 * criterion by which the value is ranked. The `callback` is bound to
* arguments; (value, index, array). * `thisArg` and invoked with 3 arguments; (value, index, array).
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -1368,8 +1369,8 @@
/** /**
* Retrieves the minimum value of an `array`. If `callback` is passed, * Retrieves the minimum value of an `array`. If `callback` is passed,
* it will be executed for each value in the `array` to generate the * it will be executed for each value in the `array` to generate the
* criterion by which the value is ranked. The `callback` is invoked with 3 * criterion by which the value is ranked. The `callback` is bound to `thisArg`
* arguments; (value, index, array). * and invoked with 3 arguments; (value, index, array).
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -1513,7 +1514,7 @@
* should be inserted into the `array` in order to maintain the sort order * should be inserted into the `array` in order to maintain the sort order
* of the sorted `array`. If `callback` is passed, it will be executed for * of the sorted `array`. If `callback` is passed, it will be executed for
* `value` and each element in the `array` to compute their sort ranking. * `value` and each element in the `array` to compute their sort ranking.
* The `callback` is invoked with 1 argument; (value). * The `callback` is bound to `thisArg` and invoked with 1 argument; (value).
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -1596,8 +1597,8 @@
* for comparisons, i.e. `===`. If the `array` is already sorted, passing `true` * for comparisons, i.e. `===`. If the `array` is already sorted, passing `true`
* for `isSorted` will run a faster algorithm. If `callback` is passed, * for `isSorted` will run a faster algorithm. If `callback` is passed,
* each value of `array` is passed through a transformation `callback` before * each value of `array` is passed through a transformation `callback` before
* uniqueness is computed. The `callback` is invoked with 3 arguments; * uniqueness is computed. The `callback` is bound to `thisArg` and invoked
* (value, index, array). * with 3 arguments; (value, index, array).
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -2222,6 +2223,58 @@
*/ */
var extend = createIterator(extendIteratorOptions); var extend = createIterator(extendIteratorOptions);
/**
* Iterates over an `object`'s enumerable own and inherited properties,
* executing the `callback` for each property. The `callback` is bound to
* `thisArg` and invoked with 3 arguments; (value, key, object).
*
* @static
* @memberOf _
* @category Objects
* @param {Object} object The object to iterate over.
* @param {Function} callback The function called per iteration.
* @param {Mixed} [thisArg] The `this` binding for the callback.
* @returns {Object} Returns the `object`.
* @example
*
* function Dog(name) {
* this.name = name;
* }
*
* Dog.prototype.bark = function() {
* alert('Woof, woof!');
* };
*
* _.forIn(new Dog('Dagny'), function(value, key) {
* alert(key);
* });
* // => alerts 'name' and 'bark' (order is not guaranteed)
*/
var forIn = createIterator(baseIteratorOptions, forEachIteratorOptions, forOwnIteratorOptions, {
'useHas': false
});
/**
* Iterates over an `object`'s enumerable own properties, executing the
* `callback` for each property. The `callback` is bound to `thisArg` and
* invoked with 3 arguments; (value, key, object).
*
* @static
* @memberOf _
* @category Objects
* @param {Object} object The object to iterate over.
* @param {Function} callback The function called per iteration.
* @param {Mixed} [thisArg] The `this` binding for the callback.
* @returns {Object} Returns the `object`.
* @example
*
* _.forOwn({ '0': 'zero', '1': 'one', '2': 'two', 'length': 3 }, function(num, key) {
* alert(key);
* });
* // => alerts 'zero', 'one', 'two', and 'length' (order is not guaranteed)
*/
var forOwn = createIterator(baseIteratorOptions, forEachIteratorOptions, forOwnIteratorOptions);
/** /**
* Produces a sorted array of the properties, own and inherited, of `object` * Produces a sorted array of the properties, own and inherited, of `object`
* that have function values. * that have function values.
@@ -2737,7 +2790,7 @@
* @example * @example
* *
* _.keys({ 'one': 1, 'two': 2, 'three': 3 }); * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
* // => ['one', 'two', 'three'] * // => ['one', 'two', 'three'] (order is not guaranteed)
*/ */
var keys = !nativeKeys ? shimKeys : function(object) { var keys = !nativeKeys ? shimKeys : function(object) {
// avoid iterating over the `prototype` property // avoid iterating over the `prototype` property
@@ -2780,7 +2833,7 @@
/** /**
* Gets the size of a `value` by returning `value.length` if `value` is a * Gets the size of a `value` by returning `value.length` if `value` is a
* string or array, or the number of own enumerable properties if `value` is * string or array, or the number of enumerable own properties if `value` is
* an object. * an object.
* *
* @static * @static
@@ -2788,7 +2841,7 @@
* @category Objects * @category Objects
* @param {Array|Object|String} value The value to inspect. * @param {Array|Object|String} value The value to inspect.
* @returns {Number} Returns `value.length` if `value` is a string or array, * @returns {Number} Returns `value.length` if `value` is a string or array,
* or the number of own enumerable properties if `value` is an object. * or the number of enumerable own properties if `value` is an object.
* @example * @example
* *
* _.size([1, 2]); * _.size([1, 2]);
@@ -3081,8 +3134,8 @@
} }
/** /**
* Executes the `callback` function `n` times. The `callback` is invoked with * Executes the `callback` function `n` times. The `callback` is bound to
* 1 argument; (index). * `thisArg` and invoked with 1 argument; (index).
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -3227,6 +3280,8 @@
lodash.first = first; lodash.first = first;
lodash.flatten = flatten; lodash.flatten = flatten;
lodash.forEach = forEach; lodash.forEach = forEach;
lodash.forIn = forIn;
lodash.forOwn = forOwn;
lodash.functions = functions; lodash.functions = functions;
lodash.groupBy = groupBy; lodash.groupBy = groupBy;
lodash.has = has; lodash.has = has;
@@ -3305,7 +3360,7 @@
lodash.take = first; lodash.take = first;
lodash.unique = uniq; lodash.unique = uniq;
// add pseudo privates used and removed during the build process // add pseudo private properties used and removed during the build process
lodash._createIterator = createIterator; lodash._createIterator = createIterator;
lodash._iteratorTemplate = iteratorTemplate; lodash._iteratorTemplate = iteratorTemplate;
lodash._shimKeys = shimKeys; lodash._shimKeys = shimKeys;

View File

@@ -37,6 +37,17 @@
'valueOf': 7 'valueOf': 7
}; };
/** Used to check problem JScript properties too */
var shadowedKeys = [
'constructor',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'toLocaleString',
'toString',
'valueOf'
];
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
/** /**
@@ -222,19 +233,68 @@
var collection = [1, 2, 3]; var collection = [1, 2, 3];
equal(_.forEach(collection, Boolean), collection); equal(_.forEach(collection, Boolean), collection);
}); });
}());
test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', function() { /*--------------------------------------------------------------------------*/
var object = {};
_.forEach(shadowed, function(value, key) {
object[key] = value;
});
deepEqual(object, shadowed); QUnit.module('lodash.forIn');
(function() {
test('iterates over inherited properties', function() {
function Dog(name) { this.name = name; }
Dog.prototype.bark = function() { /* Woof, woof! */ };
var keys = [];
_.forIn(new Dog('Dagny'), function(value, key) { keys.push(key); });
deepEqual(keys.sort(), ['bark', 'name']);
}); });
}()); }());
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
QUnit.module('lodash.forOwn');
(function() {
test('iterates over the `length` property', function() {
var keys = [],
object = { '0': 'zero', '1': 'one', 'length': 2 };
_.forOwn(object, function(value, key) { keys.push(key); });
deepEqual(keys.sort(), ['0', '1', 'length']);
});
}());
/*--------------------------------------------------------------------------*/
_.each(['forEach', 'forIn', 'forOwn'], function(methodName) {
var func = _[methodName];
QUnit.module('lodash.' + methodName + ' iteration bugs');
test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', function() {
var keys = [];
func(shadowed, function(value, key) { keys.push(key); });
deepEqual(keys.sort(), shadowedKeys);
});
test('skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', function() {
function Foo() {}
Foo.prototype.a = 1;
var keys = [];
function callback(value, key) { keys.push(key); }
func(Foo, callback);
deepEqual(keys, []);
keys.length = 0;
Foo.prototype = { 'a': 1 };
func(Foo, callback);
deepEqual(keys, []);
});
});
/*--------------------------------------------------------------------------*/
QUnit.module('lodash.groupBy'); QUnit.module('lodash.groupBy');
(function() { (function() {
@@ -315,7 +375,6 @@
test('skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', function() { test('skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', function() {
function Foo() {} function Foo() {}
Foo.prototype.a = 1; Foo.prototype.a = 1;
equal(_.isEmpty(Foo), true); equal(_.isEmpty(Foo), true);
Foo.prototype = { 'a': 1 }; Foo.prototype = { 'a': 1 };
@@ -353,8 +412,7 @@
Foo.prototype.a = 1; Foo.prototype.a = 1;
deepEqual(_.keys(Foo.prototype), ['a']); deepEqual(_.keys(Foo.prototype), ['a']);
deepEqual(_.keys(shadowed).sort(), deepEqual(_.keys(shadowed).sort(), shadowedKeys);
'constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf'.split(' '));
}); });
test('skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', function() { test('skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', function() {