Coding without loops
Loops are one of the first concepts new programmers learn about. However, they often aren't the most readable way to write code that works with lists.
This article describes what you can use to replace loops in your code.
What's wrong with loops?
Loops make sense when you think about indices and memory locations. But that's what the computer does, not how people think about problems they want to solve.
Humans think about what they want to do with each item. They usually don't care about item number 869 when going through a list - getting whatever is the next item is just fine.
It's odd that we're still using loops so often, given that there are good alternatives.
forEach
Loops are most often used to iterate over a list:
for (var i=0; i<books.length; i++) {
var book = books[i];
console.log(book.title);
}
The for loop in this example allows you to look at each book individually and then take an action based on it. In this case, we print the title of the book to the console.
We can rewrite this code using the forEach
function that's available on lists in JavaScript. As the name of the function suggests, forEach
allows you to take an action for each item in a list.
books.forEach(function(book, i){
console.log(book.title);
});
A function is used to represent the "action". If you have five books in your list the function is going to be called 5 times, and each time a different book is passed in as the first argument.
By using forEach
we can hide the underlying counting logic that the loop is using, and focus on communicating what we want to do.
filter
When working with lists, you often need to search for items that match specific criteria. For example, you could filter a list of books for a particular author.
var booksByJohnResig = [];
for (var i=0; i<books.length; i++) {
var book = books[i];
if (book.author === "John Resig") {
booksByJohnResig.push(book);
}
}
Based on the existing list of books we generate a new list that only contains books written by John Resig.
JavaScript has a filter
function that's made specifically for this purpose. It can significantly shorten our code and make it more self-explanatory:
var booksByJohnResig = books.filter(function(book){
return book.author === "John Resig";
});
This works by calling the function that's passed into filter
for every book in the list, just like forEach
does. In this case, the function performs a string comparison with the name of the author we're looking for. If our function returns true the book is included in booksByJohnResig, otherwise it isn't.
You could also go further and use Underscore's filter
method to further simplify the code:
var booksByJohnResig = _.filter(books, {author: "John Resig"});
Using Underscore also has the added advantage of providing consistent cross-browser support. The list.filter
and list.forEach
methods weren't available in Internet Explorer until version 9.
Replacing the loop with a filter function not only shortens our code, it also makes it more expressive.
map
You can use the map
function to transform each item in a list into something else.
For example, you could use map
to get a list of authors based on the list of books you have:
var authorList = books.map(function(book){
return book.author;
});
Or generate a list that contains the number of characters in each book title:
var listOfTitleLengths = books.map(function(book){
return book.title.length;
});
Calling map
doesn't modify the original list of books. Instead, it creates a new list and then generates new items for it. The function that's passed into map
is called once for each list element and returns the value that should be added to the new list.
Compared to a loop, map
shifts the focus of our code from iterating over a list to the transformation that happens to each element.
Write your own loop replacement functions
A loop is very rarely the optimal way to express what your code does.
If there's no native browser function like forEach
, filter
and map
, try to wrap the looping code you write in a helper function. Give your helper function a meaningful name so that it's clear what its purpose is.
For example, you could write this code if you needed to perform an action (i.e. call a function) for a fixed number of times:
function times(howManyTimes, action) {
for (var i=0; i<howManyTimes; i++){
action();
}
}
The times
function can then be used to print 5 random numbers, without having to mention loops in the code:
times(5, function(){
console.log(Math.random());
});
This helper function allows us to say "Print a random number 5 times" instead of "Count from 0 to 4 and print a random number every time".