List Comprehension
All the variables we have seen thus far have stored one value at a time. This is fine for distinct steps, like when we're storing a basic calculation or writing a choose-your-own-adventure story. But what if we wanted to store a lot of items, like a list of tasks for a to-do list? When using individual variables for each item, this gets awkward in a hurry.
var listItem1 = "Take out the trash";
var listItem2 = "Walk the dog";
var listItem3 = "Do laundry";
var listItem4 = "Buy groceries";
We're quickly drowning in new variable names (and not very good variable names at that). More importantly, what if we need to store more than 4 items? We've only made four variables--we'd have to create one for each possible item that we could have in our list, and imagine the complicated code to check which ones have actually been filled out. Clearly, this solution of individual variables doesn't scale very well. It's also repetitious, and that's what we call a code smell: an ugly pattern that warns us we're doing something wrong, the same way that a pungent whiff when we open the fridge is a clue that the milk is well on its way to becoming yoghurt instead.
Luckily, we have a solution. An array is a special kind of JavaScript variable type that contains other values in a numbered list. We can declare an array by putting some values between square brackets, like so:
var alphabet = ['a', 'b', 'c'];
To get values back out of the array, we use square brackets after the variable name, with a number between the brackets to say which item we want to pull out of the list. There's a catch, though: although we normally begin lists with #1, in JavaScript the first item in an array is #0, and we count up from there. The number associated with an item is its index. If you think of an array as a street of houses, the index is its house number--the number isn't the house itself, it simply tells us where to find what we're really interested in.
alphabet[2]; //gets the third item, because the list starts at index 0
'c'
Getting an array value by index is an expression like any other, which means we can store it in a variable if we want. Sometimes that's useful, because it saves us a lot of typing.
var a = alphabet[0];
console.log(a); // logs "a"
console.log(alphabet[1]); //logs "b"
What if we try to access an item in a list that doesn't exist? In that case, the value that comes back is undefined, because we haven't put anything in that section of the list yet. We can place an item in any location in the list, including an undefined location, by using the same square brackets that we used to get values out. But be careful: if we forget the square brackets entirely, we'll be talking about the list as a whole and not an item in the list.
var todo = ['take out trash', 'walk dog', 'do laundry'];
todo[3] = 'buy groceries'; //sets the fourth item (remember, we start at 0)
todo = "learn about arrays";
console.log(todo); //"learn about arrays"
//Oh no! Instead of adding a fifth item,
//we replaced the array with a string!
You can store any kind of value in an array, including another array if you want (think of it as a list of columns or rows, like a spreadsheet). Most of the time, though, we try to make arrays contain the same type of value. We might have a list of numbers, or a list of names, but we wouldn't want to mix the two up, because it will make it hard to treat those values the same--and if we can't treat them the same, why put them in the same list together?
In addition to the numbered values inside an array, they also have a special property that tells you how many items the array currently contains. This property is the length property. Since arrays start at index #0, the length will always be one more than the highest item index in the array--i.e., an array with a length of 4 will have its last item at [3].
var animals = ['dog', 'cat', 'duck'];
animals.length == 3; // true, last item is animals[2]
If you know the length, you can add to a list by adding new items at the same index as that length, because that will be one more than the last item. But keeping track of that is slow, and the resulting code is pretty ugly. It's a lot easier to add items to a list by using the built-in push() function on the list.
//create a new item the ugly way
animals[animals.length] = "tiger"
//here's the simpler way
animals.push("lion");
animals.push('bear');
//now animals has two more items: the strings "lion" and "bear"
Looping
The fundamental truths about computers are two-fold:
- Computers are very stupid, and
- Computers are very fast
In contrast, people are not good at repeating themselves. We get bored, we make mistakes, and we cut corners. One of the most important roles of a programming language, therefore, is to reduce the amount of repetition that the programmer must do. If you find yourself repeating lines of code, or (heaven forbid!) copying and pasting it from place to place, this is also a code smell. The computer is better at repetition than you are--let it do what it does best.
So how do we tell a computer to repeat an action more than once? The most common way is what's called a for loop, because it starts with the keyword for (it's not original, but it is descriptive). A loop is composed of several parts, much like our if/else statements were. Loops in JavaScript start with the for keyword, followed by parentheses and then by a block of code to repeat--so far, this is very similar to our if statements. The difference comes inside the parentheses. Loops require three statements, separated by semicolons, each of which tells the computer some necessary information about the loop:
for (setup; conditional; change) {
//...
}
- The setup contains code that's run before the loop starts. This is where we put any pre-loop initialization code.
- The conditional tells the computer how long to run the loop. As long as this conditional is true, the loop will keep executing--or, to put it another way, it will repeat until the conditional is false.
- Finally, the change is a line of code that will run after each loop. Eventually, the change should cause the conditional to be false, and end the loop.
That's a loop in theory. Here's some practice: a loop that prints the numbers from 0 to 99:
for (var i = 0; i < 100; i = i + 1) {
console.log(i);
}
This is a pretty complicated piece of code, compared to what we've done before. Let's break it down into its individual parts.
- In the setup part of the loop, we created a variable i to keep track of how many times the loop had repeated, starting with 0.
- Our condition says to repeat as long as i is less than 100, meaning that it will repeat 100 times (remember, it starts at 0).
- Finally, our change section of the loop adds 1 to i each time we reach the end of the loop--important, because otherwise it would never be greater than 99, which means our condition would never be false, and the code would run forever (or, realistically, until the browser shuts it down for being disruptive--usually about 15 seconds).
The possibility of a never-ending, infinite loop makes looping arguably the riskiest construct in JavaScript. On the other hand, it may be the most powerful construct, just in terms of being able to reduce the amount of code you write. For example, consider taking a list of prices and discounting them all by $2. Without loops, we're forced to basically treat each item in the loop as an individual variable, with all the problems that entails.
var prices = [10, 12, 20, 8, 17];
prices[0] = prices[0] - 2;
prices[1] = prices[1] - 2;
prices[2] = prices[2] - 2;
prices[3] = prices[3] - 2;
prices[4] = prices[4] - 2;
If you really look at this code critically, there's a lot of repetition going on. In fact, lines 2-6 are exactly the same, except that they change the array index for the item that's being discounted. That's a perfect case for a variable: instead of writing the array index directly, we'll use a variable and then just repeat the expression, changing the value of the variable each time. Using a loop, we can write this code only once:
var prices = [10, 12, 20, 8, 17];
for (var i = 0; i < prices.length; i = i + 1) {
prices[i] = prices[i] - 2;
}
Much shorter! Now we've taken all those lines of code and generalized them into a process that our loop can repeat.
- First we define our array, prices.
- Next we set up our loop:
- var i = 0 (the setup): we define a variable i to keep track of our array location, starting at 0 (the first index of all arrays).
- i < prices.length (the condition): we say that we will keep going as long as our index-tracking variable i is less than the length of the array. That means it will go all the way to the last item, since the index of the last item is always one less than the length property of the array.
- i = i + 1 (the change): after each repetition, we add one to i, meaning that we have moved up to the next item in the array.
- Finally, we perform the actual process inside the loop block, between the curly braces. In this case, we've taken the code from above, but instead of using a literal number between the square brackets, we just use i. As the loop runs, we'll start at prices[0], and work our way up until we trigger the condition by running out of array items (when the index is the same as the length).
I often find that it's helpful to think of loops and arrays using some kind of spatial metaphor, like a row of houses. Imagine that you're working as a pollster on a street of houses, starting with #0 and working your way to the end of the block. You'd like to know how many people answer true to a polling question. Unfortunately, you're not able to visit the houses yourself, but you have an assistant that you can send to each house, as long as you explain exactly what you want her to do. It might work something like this:
var houses = [true, false, true, true, false]; var counter = 0; for (var i = 0; i < houses.length; i = i + 1) { var current = houses[i]; if (current) { counter = counter + 1; } } console.log("Total: " + counter)
You may have noticed that we've written all these loops in a similar way. We've used the variable i each time, and the structure always tends to look like this:
//remember, i++ is the same as i = i + 1
for (var i = 0; i < arrayToLoopOver.length; i++) {
var currentItemInArray = arrayToLoopOver[i];
// ...
}
As far as programming can have a standard loop, this is the form of it. Remember this basic structure every time you want to loop over an array and do something to each item. You'll also encounter this often in other people's code. The variable names may change (although i as the index variable is traditional), but the structure of running through our list by adding one to an index counter is very, very common.
Loops and arrays are a natural pair: using arrays, you can store an unknown number of values without having to manually code for every eventuality, and loops give you a way to express what you want to do to those items without writing out every single operation. In addition to for loops, there are also while loops, but these are much more likely to go awry and never stop. A while loop doesn't have any setup or change sections inside its declarative parentheses--only a condition. It'll loop as long as that condition is true. We might mimic the array loops that we've done so far like this:
var names = ['Sarah', 'Jacob', 'Juan', 'Amon'];
var index = 0;
while (index < names.length) {
console.log(names[index]);
index++; //careful: if you forget or remove this line, the loop never ends.
}
You can think of a while loop as an if statement that repeats, which gives them a kind of elegance. for loops require us to use a slightly more awkward syntax: you have to remember which part is the setup, which is the conditional, and which is the change. You also have to remember the semicolons that separate those individual parts. But I find that in practice, for loops are harder to mess up. Because they must have all three parts, they encourage you to write loops that will eventually end--whereas, in a while loop, a math error or a forgotten line can lead to infinite looping. Still, they have their uses, and it's good to be familiar with them.
Example Code
Processing lists is a common task in every programming language, and JavaScript is no different. In the following code, we'll use a series of loops to find the highest, lowest, and total of the items in a list of numbers.
Exercises and Practice Questions
- So far, we've looped over each item in a list by adding one. What would you change about a traditional loop if you wanted to only do something to every other item instead? What about every third item?
- Likewise, what if we wanted to leave the first or last item alone? How would you change the loop to make that possible?
- In some cases, we don't want to work through the list forward, we might want to do it backward. How would you write a loop that starts at the last item, and ends with the first?