Modern JavaScript offers a few constructs for array iteration that are in many ways superior to the ol’ for loop and foreach loop. What?!? What’s wrong with the “for loop”? What’s so bad about the “foreach”? Yes, they are prominent fixtures in any JavaScript codebase and syntactically identical to the ones you get in C#, Java and many others that have been around for decades. But there are a few new hipper, stylistically pleasing and slightly more ergonomic equivalents in the language these days. But, before we get there, let’s look at the OG, the ubiquitous “for loop”:
The Classic For Loop
var employees = [ { id: 1, firstName: 'Tom', lastName: 'Vaidyan' }, { id: 2, firstName: 'Mary', lastName: 'Jones' }, { id: 3, firstName: 'John', lastName: 'Smith' }, { id: 4, firstName: 'Mark', lastName: 'Johnson' }, { id: 5, firstName: 'Jill', lastName: 'Taylor' } ]; for (let i = 0; i < employees.length; i++) { console.log(employees[i].firstName + " " + employees[i].lastName); }
While the code above is not overly complex, there are several small decisions that a developer must make:
- What should I name my counter variable?
- Should I start with a zero index? Or should I start with one?
- Based on that decision, I must decide whether to make the evaluation a less than (<) or less than or equal to (<=).
- Does it make sense to start at the end and work my way backwards to zero? i.e. i–?
While these are trivial decisions, there are a few of them and each of them is a potential bug. Moreover, the readability of the structure could be improved upon. While my contrived example fits neatly on a screen, the reality is often that you must keep track of that little “i” amidst a lot more code, perhaps nested among other loops, conditionals and other structures interwoven though it.
Map
Enter the Map construct. It allows you to iterate through an array in a succinct manner. In the example below, we use the map operator to iterate through that same array of employee objects, extracting out just what we need – their first and last names as a combined string, to form a new array of the same length.
var employees = [ { id: 1, firstName: 'Tom', lastName: 'Vaidyan' }, { id: 2, firstName: 'Mary', lastName: 'Jones' }, { id: 3, firstName: 'John', lastName: 'Smith' }, { id: 4, firstName: 'Mark', lastName: 'Johnson' }, { id: 5, firstName: 'Jill', lastName: 'Taylor' } ]; // Use map var employeeNames = employees.map(e => e.firstName + ' ' + e.lastName); console.log(employeeNames); // Output: ["Tom Vaidyan", "Mary Jones", "John Smith", "Mark Johnson", "Jill Taylor"]
Notice the lack of an iterator variable? With map, you don’t have to declare a variable to keep track of the looping process manually. That part is handled by the language, for you. Likewise, you don’t have to write a conditional that has to be continually checked through each iteration to see when a loop must stop. The map construct will iterate through each item in the array exactly once. There is no room for endless loops or room for bugs in the actual iteration process.
Filter
While map is a good choice for iterating through each item in the array and conducting an operation on each item, a filter is a good choice for querying for select items from an existing array to form a new one.
var employees = [ { id: 1, firstName: 'Tom', lastName: 'Vaidyan', fullTime: true }, { id: 2, firstName: 'Mary', lastName: 'Jones', fullTime: false }, { id: 3, firstName: 'John', lastName: 'Smith', fullTime: true }, { id: 4, firstName: 'Mark', lastName: 'Johnson', fullTime: false }, { id: 5, firstName: 'Jill', lastName: 'Taylor', fullTime: true } ]; var fullTimers = employees.filter(e => e.fullTime === true); console.log(fullTimers); /* Output: [{ firstName: "Tom", fullTime: true, id: 1, lastName: "Vaidyan" }, { firstName: "John", fullTime: true, id: 3, lastName: "Smith" }, { firstName: "Jill", fullTime: true, id: 5, lastName: "Taylor" }] */
Reduce
As implied by the name, when there is a need to reduce an array into a calculated result of some sort, reduce may prove to be a good candidate for this. What if you wanted to calculate the combined age of all your employees? Check out the reduce example below:
var employees = [ { id: 1, firstName: 'Tom', lastName: 'Vaidyan', fullTime: true, age: 40 }, { id: 2, firstName: 'Mary', lastName: 'Jones', fullTime: false, age: 30 }, { id: 3, firstName: 'John', lastName: 'Smith', fullTime: true, age: 20 }, { id: 4, firstName: 'Mark', lastName: 'Johnson', fullTime: false, age: 25 }, { id: 5, firstName: 'Jill', lastName: 'Taylor', fullTime: true, age: 35 } ]; var combinedAge = employees.reduce((ages, e) => ages + e.age, 0); console.log(combinedAge); // 150
What if you wanted to calculate the average age instead? The reduce method takes an additional parameter called index which will help in that regard.
var employees = [ { id: 1, firstName: 'Tom', lastName: 'Vaidyan', fullTime: true, age: 40 }, { id: 2, firstName: 'Mary', lastName: 'Jones', fullTime: false, age: 30 }, { id: 3, firstName: 'John', lastName: 'Smith', fullTime: true, age: 20 }, { id: 4, firstName: 'Mark', lastName: 'Johnson', fullTime: false, age: 25 }, { id: 5, firstName: 'Jill', lastName: 'Taylor', fullTime: true, age: 35 } ]; var averageAge = employees.reduce((combinedAge, e, index) => { combinedAge += e.age; return index === employees.length - 1 ? combinedAge / employees.length : combinedAge; }, 0); console.log(averageAge); // 30
In this example, through each iteration, we check to see if we are at the end of the array. If so, we return the average age. Otherwise, we are returning the accumulator, i.e., the combinedAge value so that it can be added on to, in the next iteration.
Closing Remarks
While for and foreach loops are not going anywhere, you’ll see map, reduce and filter in modern JavaScript codebases everywhere. They eliminate all the ceremony and verbosity associated with the old for loops and allow you to create new arrays from existing arrays, in a single swoop.