Array.forEach vs Lodash forEach on Sparse Arrays
This article examines the core iteration differences between native
JavaScript Array.prototype.forEach and Lodash's
_.forEach when handling sparse arrays. While both methods
serve to iterate over collections, they handle missing elements
("holes") fundamentally differently, resulting in divergent callback
execution counts, handling of undefined values, and loop
termination capabilities.
Understanding Sparse Arrays
A sparse array in JavaScript is an array where certain indices have
not been assigned a value, leaving "holes" rather than initialized
values. These are distinct from indices explicitly set to
undefined.
const sparseArray = [1, , 3]; // Index 1 is a hole, not defined1. Handling of Missing Elements (Holes)
The primary difference between native
Array.prototype.forEach and _.forEach lies in
how each engine detects and treats unassigned indices.
- Native
Array.prototype.forEach: Follows ECMAScript specifications by checking whether an index actually exists in the array using an internalHasPropertycheck. If an index is a hole, the callback function is completely skipped for that index. - Lodash
_.forEach: Iterates through the array using a length-based index loop (from index0up tolength - 1) and accesses values directly viaarray[index]. In JavaScript, accessing an empty slot via index access returnsundefined. Consequently, Lodash treats the empty slot as an element with the valueundefinedand executes the callback.
Code Demonstration
const sparseArray = [1, , 3];
// Native Array.prototype.forEach
sparseArray.forEach((value, index) => {
console.log(`Native: index ${index}, value ${value}`);
});
// Output:
// Native: index 0, value 1
// Native: index 2, value 3
// Lodash _.forEach
_.forEach(sparseArray, (value, index) => {
console.log(`Lodash: index ${index}, value ${value}`);
});
// Output:
// Lodash: index 0, value 1
// Lodash: index 1, value undefined
// Lodash: index 2, value 32. Callback Invocation Count
Because of how holes are evaluated:
- Native
forEachinvokes the callback \(N\) times, where \(N\) is the number of actual allocated properties. Fornew Array(5), the native callback runs \(0\) times. - Lodash
_.forEachinvokes the callback based strictly on the array's.lengthproperty. Fornew Array(5), the Lodash callback runs \(5\) times, passingundefinedas the value for each invocation.
3. Early Exit Support
While not strictly limited to sparse arrays, another distinct mechanical difference during iteration is loop control:
- Native
Array.prototype.forEach: Cannot be broken out of early without throwing an exception. - Lodash
_.forEach: Allows early termination of the loop across any array type (sparse or dense) by returningfalsefrom within the callback function.
Summary
When processing sparse arrays, native
Array.prototype.forEach respects sparse definitions and
skips unallocated slots, ensuring callbacks only fire for allocated
indices. Conversely, Lodash's _.forEach treats sparse
arrays as dense sequences of indices, iterating through every position
up to length and supplying undefined for
missing indices.