Object iterators

Iterating over objects using lodash functions.

JavaScript has multiple array iterators that allow you to transform an existing array into new one. The iterator methods are very attractive. They allow writing very short code that reads naturally. The iterators also eliminate 'off by one' errors common in manual loops.

1
2
3
// does given array have an even number?
function isEven(x) { return x % 2 === 0; }
[1, 2, 3].some(isEven); // true

Array.prototype.some computes a boolean value by executing the given callback over every item. As soon as callback returns a truthy value, the iteration stops. Thus we are computing single value from the array, while leaving the array unchanged. There are other array iterators that compute a single result without modifying the input array: Array.prototype.every and Array.prototype.reduce.

There are iterators that also leave the original array unchanged, but return a new array. Each item in the new array is an item from the input array passed through callback function.

1
2
function mul2(x) { return x * 2; }
[1, 2, 3].map(mul2); // [2, 4, 6]

Another great example is Array.prototype.filter that creates new array, but only from items that when passed through callback function return truthy value

1
[1, 2, 3].filter(isEven); //=> [2]

Finally, there is forEach iterator. It executes the given callback for each item. Typically, I use forEach to modify array's elements in place.

1
2
3
4
5
6
7
var numbers = [1, 2, 3];
numbers.forEach(function (x, k) {
if (isEven(x)) {
numbers[k] = mul2(x);
}
});
// numbers = [1, 4, 3]

So far we have 3 iterator types: compute a predicate, create new array based on the old one, and generic forEach iterator. There are no equivalent iterators to do the same with JavaScript objects. Luckily, a library lodash has multiple functions that operate over collections. A collection could be an array or an object. In this blog post I will show examples of _ iterators that manipulate objects.

Predicates

Let us start with Array.prototype.some equivalent. To check if there is some property inside an object that has even value we could write the code in two ways.

1
2
3
4
5
6
7
8
9
10
11
12
function isEven(x) { return x % 2 === 0; }
var obj = {
foo: 1,
bar: 2,
baz: 3
};
// first approach: without _
Object.keys(obj).some(function (key) {
return isEven(obj[key]);
});
// second approach: with _
_.some(obj, isEven);

Each iteration of _.some runs the callback function passing value and key arguments. This is very similar to Array.prototype.some callback signature that passes item and index arguments. In fact you can think of an index into array as being an integer key (instead of string keys allowed in objects). Sidenote: do not get burned by the second argument to the callback. Use unary adaptor to avoid madness.

_.some eliminates a lot of the boilerplate code. Entire Object.keys(obj) extraction is gone (it is inside _.some function). Plus the value is passed directly to the callback, without needing the manual obj[key] lookup.

There is _.every predicate that iterates over a collection

1
2
3
4
function isThreeLetter(value, key) {
return key.length === 3;
}
_.every(obj, isThreeLetter); // true

Filtering (picking)

Let us now construct new objects based on existing ones. First, let us filter properties. When dealing with objects we can filter by key, by value or by a combination of both.

The _.filter constructs a new array instead of the object, and thus we cannot use it

1
2
3
4
5
6
7
function startsWithB(s) {
return /^b/.test(s);
}
_.filter(obj, function (value, key) {
return startsWithB(key);
});
// [2, 3]

When dealing with objects, the filtering function is called _.pick.

1
2
3
4
5
6
7
8
9
10
// filtering by key
_.pick(obj, ['foo', 'baz']);
// { foo: 1, baz: 3}
function startsWithB(s) {
return /^b/.test(s);
}
_.pick(obj, function (value, key) {
return startsWithB(key);
});
// { bar: 2, baz: 3 }

Mapping (transforming)

_.transform provides generic method for constructing an object from an existing one.

1
2
3
4
_.transform(obj, function (result, value, key) {
result['_' + key] = value * 3;
});
// returns { _foo: 3, _bar: 6, _baz: 9 }

Notice that the first callback argument is the new object, and we are free to set or skip properties.

forEach

Finally, _.forEach function is for general iteration over the object's properties. The callback arguments are value, key and the object itself, so you can modify the existing object.

1
2
3
4
_.forEach(obj, function (value, key, o) {
o[key] = value + 10;
});
// obj becomes { foo: 11, bar: 12, baz: 13 }

Conclusion

I love iterators. They are slower than iterating over an array using integer indices, and sometimes callback gets unexpected arguments due to index / key. Yet, the code is much much nicer when using iterators. Using lodash allows using almost the same functions to iterate over objects as over arrays.