One of the first array methods I learned when I started writing JavaScript is the .forEach
method. It's essentially a for
loop, but written in a callback style. And with this method, we can do all sort of handy things with our arrays.
For example, we can filter
certain elements that don't match some criteria:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evenNumbers = [];
numbers.forEach((number) => {
if (number % 2 === 0) evenNumbers.push(number);
});
Or we can map
elements in an array to a new value:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const doubledNumbers = [];
numbers.forEach((number) => {
doubledNumbers.push(number * 2);
});
And we can even reduce
elements down to a single value:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let sum = 0;
numbers.forEach((number) => {
sum += number;
});
As I continued to write more code, I discovered that JavaScript arrays have native methods to handle all three of these scenarios - filter
, map
, and reduce
. Here are the three examples above, rewritten to use these methods:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evenNumbers = numbers.filter(number => number % 2 === 0);
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const doubledNumbers = numbers.map(number => number * 2);
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const sum = numbers.reduce((runningTotal, number) => runningTotal + number, 0);
But Why?
Why does JavaScript have these methods when we can use a .forEach
to accomplish the same thing?
As you can see in the examples above, these methods make for shorter code. But much more importantly, they convey meaning. In these very simple examples, it's pretty clear what's going on because we're only looking at a few lines of code. But in the real world, our code is much more complicated.
Here is a more realistic example of production code, where we want to show a student all of the assignments that they either have not submitted yet or did submit but have a chance to re-submit because their initial submission was scored too low.
const allAssignments = await getAllAssignments();
const assignmentsToSubmit = [];
allAssignments.forEach((assignment) => {
if (
assignment.dueDate >= new Date() &&
(assignment.score === undefined || assignment.score < 70)
) {
assignmentsToSubmit.push({
...assignment,
status:
assignment.score === 'undefined'
? 'unsubmitted'
: 'eligible for resubmit',
});
}
});
Compare that code to this implementation:
const allAssignments = await getAllAssignments();
const assignmentsToSubmit = allAssignments
.filter((assignment) => assignment.dueDate >= new Date())
.filter(
(assignment) => assignment.score === undefined || assignment.score < 70
)
.map((assignment) => {
return {
...assignment,
status:
assignment.score === undefined
? 'unsubmitted'
: 'eligible for resubmit',
};
});
The second example has a few things going for it that the first does not:
assignmentsToSubmit
is declared and assigned at the same time. We don't need to scroll down to figure out how it's being populated.- We have separated out our filter criteria, and it's explicitly clear that we are filtering the array of assignments. Those
.filter
statements are crystal clear as to what they are doing. - We are adding an extra property,
status
, that does not exist on the originalassignment
objects. The presence of.map
makes it clear that this is happening. If we didn't need to add this property, we wouldn't need the.map
function at all, and we would just be left with the two.filter
statements.
Isn't This Wasteful?
One thing that may have caught your eye is that in the second example above, we're looping over the array three times, where as in the first example we're only looping over the array once. That feels very wasteful. Why do more work than we need to?
The answer is that readability and maintainability are more important than the micro-optimization we can achieve by only looping over the array once. In this real world example, how many assignments can we realistically expect to be looping over? Even if the student was in a class that handed out an assignment every single day for a full year, that's still only an array of 365 elements. Looping over an array that small once instead of three times is going to save you a millisecond or two.
If you're working in a system where a millisecond or two is important to save, or if your array has a very large number of elements and the cost savings of only looping once will be significant, then by all means use the .forEach
approach. There have been scenarios where I have had to modify my code to be more performant. But given the choice between performance and readability, I always start with the more readable code and only make it more performant (and typically less readable) when performance becomes an actual issue.