Learn JavaScript: Novice to Ninja

Chapter 3: Arrays, Logic, and Loops

In this chapter we’ll look at some of the data structures used in JavaScript to store lists of values. These are called arrays, sets, and maps. We’ll also look at logical statements that allow us to control the flow of a program, as well as loops that allow us to repeat blocks of code over and over again.

This chapter will cover the following topics:

  • Array literals
  • Adding and removing values from arrays
  • Array methods
  • Sets
  • Maps
  •  if and else statements
  •  switch statements
  •  while loops
  •  do … while loops
  •  for loops
  • Iterating over a collection
  • Project ― we’ll use arrays, loops and logic to ask multiple questions in our quiz

Arrays

An array is an ordered list of values. To create an array literal, simply write a pair of square brackets:

const myArray = [];<< []

You can also use an array constructor function:

const myArray = new Array();<< []

Both of these produce an empty array object, but it’s preferable to stick to using array literals because of avariety of reasons… and they require less typing!

Arrays are not primitive values but a special built-in object, as we can see when we use the typeof operator:

typeof []<< 'object'

You can read more about creating and manipulating arrays inthis article.

Initializing an Array

We can create an empty array literal called heroes with the following code:

const heroes = [];

We can find out the value of element 0 in the heroes array using the following code:

heroes[0]<< undefined

To access a specific value in an array, we write its position in the array in square brackets (this is known as its index). If an element in an array is empty, undefined is returned.

Adding Values to Arrays

To place the string ‘Superman’ inside the first element of our heroes array, we can assign it to element 0 , like so:

heroes[0] = 'Superman';

Each item in an array can be treated like a variable. You can change the value using the assignment operator = . For example, we can change the value of the first item in the heroes array to ‘Batman’:

heroes[0] = 'Batman';

We can add more values to our array by assigning them to other indices:

heroes[1] = 'Wonder Woman';heroes[2] = 'Flash';

We can use the index notation to add new items to any position in the heroes array:

heroes[5] = 'Aquaman';

We can look at the heroes array by simply typing its name into the console:

heroes;<< ['Batman', 'Wonder Woman', 'Flash', undefined, undefined, 'Aquaman']

Here we can see that the sixth item (with an index of 5 ) has been filled with the string ‘Aquaman’. This has made the array longer than it was before, so all the other unused slots in the array are filled by the value undefined .

Creating Array Literals

We can create an array literal using square brackets that already contain some initial values, so there’s no need to add each value one by one. Here’s an example:

const avengers = ['Captain America', 'Iron Man', 'Thor', 'Hulk'];<< ['Captain America', 'Iron Man', 'Thor', 'Hulk']

You don’t even have to use the same types of items inside an array. This array contains a variety different data types, as well as an empty array object:

const mixedArray = [ null, 1, [], 'two', true ];

Removing Values from Arrays

The delete operator will remove an item from an array:

delete avengers[3];<< true

If we look at the avengers array, we can see that the fourth entry, ‘Hulk’ (with an index of 3 ), has indeed been removed … but it has been replaced by a value of undefined :

avengers;<< ['Captain America', 'Iron Man', 'Thor', undefined]

Watch out for this as it can even trip up experienced programmers. Thevaluethat was in position 3 (‘Hulk’) has been deleted from the array, but the space that it occupied is still there and contains a value of undefined . This means the array still has the same number of elements, and the position can still be referenced as an index, but it will just return undefined :

avengers[3];<< undefined

Destructuring Arrays

Destructuringan array is the concept of taking values out of an array and presenting them as individual values.

Destructuring allows us to assign multiple values at the same time, using arrays:

const [x,y] = [1,2];

Even though the assignment is made using arrays, each individual variable exists on its own outside the array. We can see this by checking the value of each variable:

x<< 1y<< 2

Destructuring also gives us a neat way of swapping the value of two variables over:

[x,y] = [y,x];x<< 2y<< 1

Before ES6, a temporary variable would have to be used to achieve the same result:

const temp = x;x = y;y = temp;

You can read more about destructuring inthis article.

Array Properties and Methods

Arrays are a powerful weapon in a JavaScript ninja’s toolkit and have some useful methods. To demonstrate these, we’re going to use the following avengers array that is similar to the one we produced earlier. You’ll need to create a reference to it by entering the following into the console:

const avengers = ['Captain America', 'Iron Man', 'Thor', 'Hulk', 'Hawkeye', 'Black Widow'];

To find the length of an array, we can use the length property:

avengers.length;<< 6

The length property can be used as part of the index to find the last item in an array:

avengers[avengers.length - 1];<< 'Black Widow'

Notice that we have to subtract 1 from the length property. This is because the index starts at 0, so the last item in the array will have an index of one less than the array’s length.

The length property is mutable, meaning you can manually change it:

avengers.length = 8;<< 8avengers<< ['Captain America', 'Iron Man', 'Thor', 'Hulk', 'Hawkeye', 'Black Widow', undefined, undefined]

As you can see, if you make the array longer, the extra slots will be filled in with undefined :

avengers.length = 3<< 3avengers<< ['Captain America', 'Iron Man', 'Thor']

If you make the array shorter than it already is, all the extra elements will be removed completely.

Pop, Push, Shift, and Unshift

To remove the last item from an array, we can use the pop() method:

avengers.pop();<< 'Thor'

The method returns the last item of the array, but the array no longer contains that item. If we take a look at the avengers array, we’ll see that it no longer contains the string ‘Thor’:

avengers<< ['Captain America', 'Iron Man']

The shift() method works in a similar way to the pop() method, but this removes thefirstitem in the array:

avengers.shift();<< 'Captain America'

The push() method appends a new value to the end of the array.

avengers.push('Thor');<< 2

The return value is the new length of the array:

The unshift() method is similar to the push() method, but this appends a new item to thebeginningof the array:

avengers.unshift('Captain America');<< 3

Merging Arrays

The concat() method can be used to merge an array with one or more arrays:

avengers.concat(['Hulk','Hawkeye', 'Black Widow']);<< ['Captain America', 'Iron Man', 'Thor', 'Hulk', 'Hawkeye', 'Black Widow']

Note that this does not change the avengers array, it simply creates another array combining the two arrays. You can use assignment to update the avengers array to this new array:

avengers = avengers.concat(['Hulk','Hawkeye', 'Black Widow']);<< ['Captain America', 'Iron Man', 'Thor', 'Hulk', 'Hawkeye', 'Black Widow']

Now if we check the value of the avengers array we can see that it now contains the strings ‘Hulk’, ‘Hawkeye’ and ‘Black Widow’:

avengers<< ['Captain America', 'Iron Man', 'Thor', 'Hulk', 'Hawkeye', 'Black Widow']

An alternative is to use the new spread operator that was added to ES6. The spread operator is three dots, ... that are placed in front of an array, with the effect of spreading out the elements of that array. This can be used to spread the elements of two arrays and put them together in a new array, like so:

avengers = [ ...avengers, ...['Hulk','Hawkeye', 'Black Widow'] ];<< ['Captain America', 'Iron Man', 'Thor', 'Hulk', 'Hawkeye', 'Black Widow']

In the example above, the spread operator is used on the avengers array as well as the new array literal. This has the effect of spreading out all the values in each array, which allows them to be placed inside a new array.

The join() Method

The join() method can be used to turn the array into a string that comprises all the items in the array, separated by commas:

avengers.join();<< 'Captain America, Iron Man, Thor, Hulk, Hawkeye, Black Widow'

You can choose a separator other than a comma by placing it inside the parentheses. Let’s try using an ampersand:

avengers.join(' & ');<< 'Captain America & Iron Man & Thor & Hulk & Hawkeye & Black Widow'

Slicing and Splicing

The slice() method creates a subarray; effectively chopping out a slice of an original array, starting at one position and finishing at another. For example, if we wanted to find the 3rd and 4th item in our array we would use the following code:

avengers.slice(2,4) // starts at the third item (index of 2) and finishes at the fourth (the item with index 4 is not included)<< ['Thor', 'Hulk']

Note that this operation is non-destructive ― no items are actually removed from the array, as we can see if we take a look at the avengers array:

avengers<< ['Captain America', 'Iron Man', 'Thor', 'Hulk', 'Hawkeye', 'Black Widow']

The splice() method removes items from an array then inserts new items in their place. For example, the following code removes the string ‘Hulk’ and replaces it with ‘Scarlett Witch’::

avengers.splice(3, 1, 'Scarlet Witch');<< ['Hulk']

This is a destructive operation as it changes the value of the array, as we can see below:

avengers<< ['Captain America', 'Iron Man', 'Thor', 'Scarlet Witch', 'Hawkeye', 'Black Widow']

The first number in the parentheses tells us the index at which to start the splice. In the example we started at index 3, which is the fourth item in the array (‘Hulk’). The second number tells us how many items to remove from the array. In the example, this was just one item. Every value after this is then inserted into the array in the same place the other items were removed. In this case, the string ‘Scarlet Witch’ is inserted into the array, starting at the fourth item. Notice the splice() method returns the items removed from the array as a subarray. So in the example, it returned the array ['Hulk'] .

The splice() method can also be used to insert values into an array at a specific index without removing any items, by indicating that zero items are to be removed:

avengers.splice(4,0,'Quicksilver');<< []

Notice that an empty array is returned (because nothing was removed), but the new value of ‘Quicksilver’ has been inserted at position 4, which we can see if we look at the avengers array:

avengers<< [ 'Captain America','Iron Man', 'Thor', 'Scarlet Witch', 'Quicksilver', 'Hawkeye', 'Black Widow' ]

The splice() method is a particularly flexible method as it can be used to insert or remove values from an array. Be careful, though, as splice() is a destructive method which means it changes the array permanently.

We saw earlier that we can use the delete operator to remove an item from an array. Unfortunately, this leaves a value of undefined in its place. If you want to remove a value completely, you can use the splice() method with a length of 1 and without specifying any values to add:

avengers.splice(2,1); // will remove the item at index 2 (i.e. the third item in the array)<< [ 'Thor' ]

As you can see, the value that is removed will be returned as an array containing that value.

If we now look at the avengers array, we can see that the string ‘Thor’ has been removed completely:

avengers;<< ['Captain America', 'Iron Man', 'Scarlet Witch', 'Quicksilver', 'Hawkeye', 'Black Widow']

Reverse

We can reverse the order of an array using the reverse() method:

avengers.reverse();<< ['Black Widow', 'Hawkeye', 'Quicksilver', 'Scarlet Witch', 'Iron Man', 'Captain America']

Note that this changes the order of the array permanently.

Sort

We can sort the order of an array using the sort() method:

avengers.sort();<< ['Black Widow', 'Captain America', 'Hawkeye', 'Iron Man', 'Quicksilver', 'Scarlet Witch']

It is alphabetical order by default for String objects. Note that this also changes the order of the array permanently.

Numbers Get Sorted Alphabetically

Numbers are also sorted alphabetically (that is, by their first digit, rather than numerically), so 9 will come after 10 when you try to sort an array of numbers:

[5, 9, 10].sort();<< [10, 5, 9]

This can be fixed using a callback, which is a function that is passed as an argument to the sort() method when it is called.

We’ll cover how to do this in Chapter 4

Finding if a Value is in an Array

We can find out if an array contains a particular value using the indexOf() method to find the first occurrence of a value in an array. If the item is in the array, it will return the index of the first occurrence of that item:

avengers.indexOf('Iron Man');<< 3

If the item is not in the array, it will return -1 :

avengers.indexOf('Thor');<< -1

ES6 also introduced the includes() method. This returns a boolean value depending on whether the array contains a particular element or not:

avengers.includes('Iron Man');<< trueavengers.includes('Thor');<< false

You can also add an extra parameter to indicate which index to start the search from:

avengers.includes('Black Widow', 1); // will start the search from the second element in the array<< false

Multidimensional Arrays

You can even have an array of arrays, known as a multidimensional array. This could be used to create a coordinate system, for example:

const coordinates = [[1,3],[4,2]];<< [[1,3],[4,2]]

To access the values in a multidimensional array, we use two indices: one to refer to the item’s place in the outer array, and one to refer to its place in the inner array:

coordinates[0][0]; // The first value of the first array<< 1coordinates[1][0]; // The first value of the second array<< 4coordinates[0][1]; // The second value of the first array<< 3coordinates[1][1]; // The second value of the second array<< 2

The spread operator that we met earlier can be used toflattenmulti-dimensional arrays. Flattening an array involves removing all nested arrays so all the values are on the same level in the array. You can see an example of a flattened array below:

const summer = ['Jun', 'Jul', 'Aug'];const winter = ['Dec', 'Jan', 'Feb'];const nested = [ summer, winter ];<< [ [ 'Jun', 'Jul', 'Aug' ], [ 'Dec', 'Jan', 'Feb' ] ]const flat = [...summer, ...winter];<< [ 'Jun', 'Jul', 'Aug', 'Dec', 'Jan', 'Feb' ]

A summary of creating and manipulating arrays can be found inthis post on SitePoint.

Sets

Sets were introduced to the specification in ES6. A set is a data structure that represents a collection of unique values, so it cannot include any duplicate values. They are similar in concept to amathematical set, although (for the time being at least) they don’t contain mathematical set operations such as union, intersection and product.

Sets offer a useful way to keep track of data without having to check if any values have been duplicated. It’s also quick and easy to check if a particular value is in a set, which can be a slow operation if an array is used.

Creating Sets

An empty set is created using the new operator and Set() constructor:

const list = new Set();

There is, at the time of writing, no literal notation for creating sets.

Adding Values to Sets

Values can be placed into a set using the add method:

list.add(1);<< Set { 1 }

Multiple items can be added to the set by repeating the add() method:

list.add(2).add(3).add(4);<< Set { 1, 2, 3, 4 }

If you try to add a value that is already contained in the set, then the operation is simply ignored:

list.add(1);<< Set { 1, 2, 3, 4 }

Multiple values can be added to a set in one go by placing them inside an array that is provided as an argument:

const numbers = new Set([1,2,3]);

To see the contents of a set, simply enter the name of the variable that refers to it:

numbers<< Set { 1, 2, 3 }

If any values are repeated in the array, then they will only appear once in the set:

const moreNumbers = new Set([7,7,7,7,7,8,8,8,9,9]);moreNumbers<< Set {7,8,9}

This gives a convenient way of eliminating any duplicate values from an array in a single operation.

If a string is used as the argument then each character will be added as a separate element, with any repeated characters ignored:

const letters = new Set('hello');letters<< Set { 'h', 'e', 'l', 'o' }

If you want to add separate words, you need to use the add() method:

const words = new Set().add('the').add('quick').add('brown').add('fox')words<< Set { 'the', 'quick', 'brown', 'fox' }

All non-primitive values, such as arrays and objects, are considered unique values, even if they contain the same values. On the face of it, this appears to allow duplicate values appear in a set:

const arrays = new Set().add([1]).add([1]);arrays<< Set { [ 1 ], [ 1 ] }

The two arrays may look the same, but are considered different objects. This can be seen with the following strict equality test:

[1] === [1];<< false

Type coercion is not used when values are added to a set, so the string ‘2’ will be added as a new entry, even if the number 2 is already an element of the set:

const mixedTypes = new Set().add(2).add('2');mixedTypes<< Set { 2, '2' }

Set Methods

The number of values in a set can be found using the size() method:

const jla = new Set().add('Superman').add('Batman').add('Wonder Woman');jla<< Set { 'Superman', 'Batman', 'Wonder Woman' }jla.size();<< 3

The has() method can be used to check if a value is in a set. This returns a boolean value of true or false :

jla.has('Superman');<< truejla.has('Green Lantern');<< false

The has() method that sets use is a very efficient operation and much faster than using the includes() or indexOf() methods to check if a value is in an array, as can be seen inthis benchmark test.

Setsdo nothave index notation for inspecting individual entries, so you can’t find the value of the first element in a set like this:

jla[0]<< undefined

Removing Values From Sets

The delete() method can be used to remove a value from a set. This returns a boolean value of true if the value was removed from the set, or false if the value wasn’t in the set and couldn’t be removed:

jla.delete('Superman');<< truejla.delete('Flash');<< false

The clear() method can be used to removeallvalues from a set:

jla.clear();jla<< Set {}jla.size<< 0

Converting Sets to Arrays

A set can be converted into an array by placing the set, along with thespread operatordirectly inside an array literal.

To demonstrate this, first we’ll create a set of three items:

const shoppingSet = new Set().add('Apples').add('Bananas').add('Beans');shoppingSet<< Set { 'Apples', 'Bananas', 'Beans' }

Then we convert it into an array:

const shoppingArray = [...shoppingSet]shoppingArray<< [ 'Apples', 'Bananas', 'Beans' ]

It’s also possible to use the Array.from() method to convert a set into an array. The following code would achieve the same result as using the spread operator above:

const shoppingSet = new Set().add('Apples').add('Bananas').add('Beans');const shoppingArray = Array.from(shoppingSet);

By combining this use of the spread operator with the ability to pass an array to the new Set() constructor, we now have a convenient way to create a copy of an array with any duplicate values removed:

const duplicate = [3, 1, 4, 1, 5, 9, 2, 6 ,5,3,5,9];<< [ 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 9 ]const nonDuplicate = [...new Set(repeatedArray)];<< [ 3, 1, 4, 5, 9, 2, 6 ]

Weak Sets

When objects are added to sets, they will be stored there as long as the set exists, even if the original reference to the object is removed. The technical term for this is the object is prevented from beinggarbage-collected, which can cause amemory leak. This can be seen in the following example:

let array = [1,2,3];const strong = new Set().add(array);array = null; // remove reference to the originalstrong<< Set { [ 1, 2, 3 ] }

The array still exists inside the set and we can get the original value of array back using the spread operator:

array = [...strong][0];array<< [1,2,3]

Memory Leaks

Amemory leakoccurs when a program retains references to values that can no longer be accessed in its memory. This means that memory is being used to store values that are no longer required by the program, effectively wasting system resources.

Memory leaks can cause problems by gradually reducing the overall memory available, which can cause the program, or even the entire system, to run more slowly.

Most modern programming language, including JavaScript, employ various dynamic memory management techniques such asgarbage collection, which is the process of automatically removing items from memory that are no longer required by the program. Some languages, such as C++, require the programmer to manually manage memory by removing items from memory once they are finished with.

Weak sets avoid this situation by garbage collecting any references to a ‘dead object’ that’s had its original reference removed.

To create a weak set, the new operator and the WeakSet() constructor in the same way that we created a set:

const weak = new WeakSet();

Only non-primitive data types can be added to weak sets. Trying to add primitive values will throw a type error:

weak.add(2)<< TypeError: Invalid value used in weak set

Apart from these restrictions, weak sets behave in the same way as regular sets, and have the has()add() , and delete() methods that work in the same way.

In the next example we can see what happens if we add an array to a weak set:

const array = [1,2,3];weak.add(array);<< WeakSet {}

Because weak maps use weak references to objects, they don’t have access to a list of values they contain. This makes the return value in the example look as though the weak set is empty, when, in fact it isn’t.

We can confirm it does indeed contain the array object by using the has() method:

weak.has(array);<< true

We can remove the array from the weak set using the delete() method:

weak.delete(array);<< true

And check it’s been removed using the has() method again:

weak.has(array);<< false

Maps

Mapswere another data structure introduced in the ES6 specification. They are a convenient way of keeping a list of key and value pairs, and are similar to ‘hashes’, or ‘hash tables’ or ‘dictionaries’ in other programming languages.

At first glance, maps appear to be similar to JavaScript objects (covered in Chapter 5), but they have some noticeable differences:

  • Objects are limited to using strings for key values, whereas maps can use any data type as a key.
  • There is no efficient way to find the number of key-value pairs an object has, whereas this is easy to do with maps using the size property.
  • Objects have methods that can be called (see Chapter 5) and prototypes that can be used to create a chain of inheritance (see Chapter 12), whereas maps are solely focused on the storage and retrieval of key-value pairs.
  • The value of an object’s properties can be accessed directly, whereas maps restrict you to using the get() method to retrieve any values.

Creating Maps

An empty map object can be created using the new operator and Map() constructor:

const romanNumerals = new Map();

There is, at the time of writing, no literal notation for creating maps.

Adding Entries To Maps

The set() method can be used to add a key and value pair to a map. The first value is the key and the second is the value:

romanNumerals.set(1,'I');<< Map { 1 => 'I' }

The return value shows the mapping with the key connected to the value using the ‘hash rocket’ symbol ( => ).

Multiple items can be added to the set by repeatedly calling the set() method in one go:

romanNumerals.set(2,'II').set(3,'III').set(4,'IV').set(5,'V');<< Map { 1 => 'I', 2 => 'II', 3 => 'III', 4 => 'IV', 5 => 'V' }

Map Methods

A map is a bit like a dictionary where you can look up a value based on the key. To look up a value, we can use the get() method:

romanNumerals.get(4);<< 'IV'

The has() method can be used to check if a particular key is in a map. This returns a boolean value of true or false :

romanNumerals.has(5);<< trueromanNumerals.has(10);<< false

A map can be created with multiple values by using a nested array as a parameter:

const heroes = new Map([ ['Clark Kent','Superman'],['Bruce Wayne', 'Batman']]);

The number of key and value pairs in a map can be found by querying the size property:

heroes.size<< 2

Removing Entries From Maps

The delete() method can be used to remove a key and value pair from a map. This returns a boolean value of true if the value was removed or false if it wasn’t in the map. To delete a specific value, you need to specify the key in parentheses:

heroes.delete('Clark Kent');<< trueheroes.size<< 1

The clear() method will removeallkey and value pairs from a map:

heroes.clear();heroes.size;<< 0

Converting Maps to Arrays

Maps can be converted into a nested array of key-value pairs in a similar way to sets; using either the spread operator:

[...romanNumerals]<< [ [ 1, 'I' ], [ 2, 'II' ], [ 3, 'III' ], [ 4, 'IV' ], [ 5, 'V' ] ]

… or the Array.from() method:

Array.from(romanNumerals)<< [ [ 1, 'I' ], [ 2, 'II' ], [ 3, 'III' ], [ 4, 'IV' ], [ 5, 'V' ] ]

Weak Maps

Weak mapswork in the same way as weak sets. They are the same as maps, except their keys cannot be primitives, and the garbage collector will automatically remove any dead entries when the reference to the original object is deleted.

To create a weak map, the new operator is used along with the WeakMap() constructor:

const weak = new WeakMap();

Weak maps can use the has()get()set() and delete() methods in the same way as a regular map.

Weak maps and sets are useful for optimizing memory usage and avoiding memory leaks, but they’re also limited in that they don’t have access to all the methods their regular counterparts have. For example, you cannot use the size() method to see how many entries they contain. The choice of which to use will depend on what you plan to use them for.

Logic

In this section, we’ll begin to look at logical conditions that allow you to control the flow of a program by running different blocks of code, depending on the results of certain operations.

 if Statements

An if statement has the following structure:

if (condition) {// code to run if condition is true}

The code inside the block will only run if the condition in parentheses is true. If the condition is not a boolean value, it will be converted to a boolean, depending on whether or not it is truthy or falsy (see Chapter 2).

Here is an example that will only display the alert message if the value of the age variable is less than 18:

const age = 23;if (age < 18) {console.log('Sorry, you are not old enough to play this game');}

Try changing the value of the age variable to a value below 18 as it does in this code, and the alert box will show.

const age = 12;if (age < 18) {console.log('Sorry, you are not old enough to play this game');}

 else Statements

The else keyword can be used to add an alternative block of code to run if the condition is false. An if ... else statement looks like this:

if (condition) {// code to run if condition is true} else {// code to run if condition is false}

As an example, we can test if a number is even or odd using the following code:

const n = 12;if (n%2 === 0) {console.log('n is an even number');} else {console.log('n is an odd number');}<< 'n is an even number'

This uses the % operator that we met in the previous chapter to check the remainder when dividing the variable n by 2. All even numbers leave no remainder when divided by 2, so we can test to see if n%2 is equal to zero; if it is, n must be even. If n is not even, then it must be odd.

Ternary Operator

A shorthand way of writing an if ... else statement is to use the ternary operator, ? , which takes three operands in the following format:

condition ? (//code to run if condition is true) : (//code to run if condition is false)

Here’s the example for testing if the variable n is odd or even, rewritten to use the ternary operator:

const n = 5;n%2 === 0 ? console.log('n is an even number') : console.log('n is an odd number');<< 'n is an odd number'

We could make the example even shorter by placing the ternary operator inside a template string:

console.log(`n is a ${(n%2 === 0)? 'even' : 'odd'} number`);

This will evaluate the ternary operator and place the result directly inside the template string that is then logged in the console.

The ternary operator can make your code more succinct, but can also make it harder to read, so think carefully before using it.

 Switch Statements

You can actually string lots of if and else statements together to make a logical decision tree:

if (number === 4) {console.log('You rolled a four');} else if (number === 5) {console.log('You rolled a five');} else if(number === 6){console.log('You rolled a six');} else {console.log('You rolled a number less than four');}

The switch operator can be used to make your code easier to follow when there are lots of conditions to test. The example above can be rewritten using a switch statement like so:

switch (number) {    case 4:    console.log('You rolled a four');    break;    case 5:    console.log('You rolled a five');    break;    case 6:    console.log('You rolled a six');    break;    default:    console.log('You rolled a number less than four');    break;}

The value you are comparing goes in parentheses after the switch operator. A case keyword is then used for each possible value that can occur ( 45 , and 6 in the example above). After each case statement is the code that that needs to be run if that case occurs.

It is important to finish each case block with the break keyword, as this stops any more of the case blocks being executed. Without a break statement, the program will “fall through” and continue to evaluate subsequent case blocks. This is sometimes implemented on purpose, but it is a hack and can be confusing. For this reason it should be avoided ― a ninja programmer always finishes a case block with a break !

The default keyword is used at the end for any code than needs to be run if none of the cases are true.

Loops

Loops will repeat a piece of code over and over again according to certain conditions.

 While Loops

We’ll start by looking at a while loop. This will repeatedly run a block of code while a certain condition is true, and takes the following structure:

while (condition) {// do something}

Here’s an example that will count down from 10, logging a line from the famous song each time:

let bottles = 10;while (bottles > 0){    console.log(`There were ${bottles} green bottles, hanging on a wall. And if one green bottle should accidentally fall, there'd be ${bottles-1} green bottles hanging on the wall`);    bottles--;}

We start by declaring a variable called bottles . Any variables that are used in the loop must be initialized before the loop is run, otherwise there will be an error when they are mentioned.

The loop starts here with the while keyword and is followed by a condition and a block of code. The condition in the example is that the value of the bottles variable has to be greater than zero. This basically means “keep repeating the block of code, as long as the number of bottles is greater than zero”.

The block of code uses the alert function to display a message about the number of bottles, then uses the decrement operator to decrease the bottles variable by one.

Here’s a more concise way of writing the same loop that moves the increment into the condition:

let bottles = 11;while (--bottles){    console.log(`There were ${bottles} green bottles, hanging on a wall. And if one green bottle should accidentally fall, there'd be ${bottles-1} green bottles hanging on the wall`);}

The reason this code works is because the loop will continue while the bottles variable is true, and before each loop, the value of the bottles variable decreases by 1 . When the bottles variable reaches 0 , it is not true anymore (remember that 0 is a falsy value) so the loop will stop. Notice that you have to start with one more bottle (11) as it will be decreased by one even before the first block is run.

Infinite Loops

It is important that the condition in a while loop will be met at some point, otherwise your code will get stuck in an infinite loop that could possibly crash the program.

Consider the following loop:

let n = 1;while(n>0){    console.log('Hello');    n++;}

This loop will keep running, as the variable n willalwaysbe above zero. Most browsers will warn you that there is a slow running script when this happens and give you the option to stop it. If not, you can kill the process by closing the tab or restarting the browser. Obviously you want to avoid this happening, though; especially with public-facing code.

 do ... while Loops

do ... while loop is similar to a while loop. The only difference is that the condition comesafterthe block of code:

do {    do something} while(condition)

This means that the block of code will always be run at least once, regardless of the condition being true or not.

Here’s the same example we saw before, rewritten as a do ... while loop:

let bottles = 10;do {    console.log(`There were ${bottles} green bottles, hanging on a wall. And if one green bottle should accidentally fall, there'd be ${bottles-1} green bottles hanging on the wall`);    bottles--;} while (bottles > 0)

 For Loops

 For loops are probably the most commonly type of loop used in JavaScript, and take the following form:

for (initialization ; condition ; after) { do something }

Theinitializationcode is runbeforethe loop starts and is usually employed to initialize any variables used in the loop. Theconditionhas to be satisfied for the loop to continue. Theaftercode is what to do aftereach iterationof the loop, and it is typically used to increment a counter of some sort.

Here’s the green bottles example written as a for loop:

for (let bottles = 10 ; bottles > 0 ; bottles--) {    console.log(`There were ${bottles} green bottles, hanging on a wall. And if one green bottle should accidentally fall, there'd be ${bottles-1} green bottles hanging on the wall`);}

Each part of a for loop are optional, and the code could be written as:

let bottles = 10; // bottles is initialized here insteadfor ( ; bottles > 0 ; ) { // empty initialization and increment    console.log(`There were ${bottles} green bottles, hanging on a wall. And if one green bottle should accidentally fall, there'd be ${bottles-1} green bottles hanging on the wall`);    bottles--; // increment moved into code block}

As you can see, it’s possible to use a while loop, a do ... while loop, or a for loop to achieve the same results. A for loop is considered clearer, as all the details of the loop (the initialization, condition and increment) are shown in one place and kept out of the actual code block.

Nested for Loops

You can place a loop inside another loop to create a nested loop. It will have an inner loop that will run all the way through before the next step of the outer loop occurs.

Here’s an example that produces a multiplication table up to 12 x 12:

for(let i=1 ; j<13 ; i++){for(let i=1 ; j<13 ; j++){    console.log(`${j} multiplied by ${i} is ${i*j}`);    }}

The outer loop counts up from i=1 to i=12 . For every iteration of the outer loop, the inner loop counts up from j=1 to j=12 . This means that it starts in the first iteration with i = 1 and j = 1 , producing the following output that is logged to the console:

<< 1 multiplied by 1 is 1

In the next iteration, we are still inside the inner loop, so i remains as 1, but j is incremented to 2, giving:

<< 1 multiplied by 2 is 2

 j continues to increase until it reaches 12. After this, we leave the inner loop and return to the outer loop, where i increases to 2. We then re-enter the inner loop and j is reset back to 1 and begins counting up to 12 again. This continues until the last iteration produces the line:

<< 12 multiplied by 12 is 144

Looping over Arrays

for loop can be used to iterate over each value in an array. If we take our avengers array example from earlier, we can create a for loop that outputs each item in the array to the console using the following loop:

for(let i=0, max=avengers.length; i < max; i++){console.log(avengers[i]);}<< 'Black Widow'<< 'Captain America'<< 'Hawkeye'<< 'Iron Man'<< 'Quicksilver'<< 'Scarlet Witch'

There are a few points to note in this example. Array indices start their numbering at zero, so make sure the value of the initial counter in the for loop also starts at zero. We want the loop to continue until it reaches the length of the array; this can be set as the variable max in the initialization part of the for loop, then the condition becomes i < max . This is preferable to using i < avengers.length because then the length of the avengers array would have to be calculated after every pass through the loop. This might not sound all that important, but it can make a big difference to the speed of the program when using large arrays.

ES6 introduced an improved iterator function for arrays called a for-of loop that uses a slightly different syntax:

for(const value of avengers){console.log(value);}<< 'Black Widow'<< 'Captain America'<< 'Hawkeye'<< 'Iron Man'<< 'Quicksilver'<< 'Scarlet Witch'

This replaces all of the setup of a ‘for’ loop with a variable ( value in the example above) that represents the value of each element in the array. Note that this variable needs to be declared using const .

Looping Over Sets

Sets areenumerable, which means they have methods that allow you to loop over each value in the set. The loop will iterate over each value inthe same order they were added to the set. To demonstrate this, we will use the set of letters that we created earlier:

const letters = new Set('hello');

We can iterate over each value in the set using a for-of loop, like so:

for(const letter of letters) {    console.log(letter);}<< helo

Note that weak sets arenon-enumerable, so it’s not possible to loop over them in this way.

Looping Over Maps

Maps are also enumerable, so it’s also possible to loop over a map in a similar way to a set. The loop will iterate over each key-value pair in the same order as they were added to the map. For example let’s use the romanNumerals map that we created earlier:

const romanNumerals = new Map();romanNumerals.set(1,'I').set(2,'II').set(3,'III').set(4,'IV').set(5,'V');romanNumerals<< Map { 1 => 'I', 2 => 'II', 3 => 'III', 4 => 'IV', 5 => 'V' }

Every map object has a keys() method lets us iterate over each key with the following for-of loop:

for(const key of romanNumerals.keys()) {    console.log(key);}<< 12345

There is also a values() method that lets us iterate over the values in a similar way:

for(const value of RomanNumerals.values()) {    console.log(value);}<< IIIIIIIVV

If you want to access both the key and the value, you can use the entries() method:

for(const [key,value] of RomanNumerals.entries()) {    console.log(`${key} in Roman numerals is ${value}`);}<< 1 in Roman numerals is I2 in Roman numerals is II3 in Roman numerals is III4 in Roman numerals is IV5 in Roman numerals is V

Note that weak maps are alsonon-enumerable, so it isn’t possible to loop over them using any of the methods shown above.

Quiz Ninja Project

Now we’ve reached the end of the chapter, it’s time to use what we’ve learned to add some features to our Quiz Ninja project.

We’ll start by creating an array called quiz that contains all the questions and answers. Each element in quiz will be a nested array that contains the question as its first element and the answer as its second element. Open up main.js and add the following code at the top:

const quiz = [    ["What is Superman's real name?","Clark Kent"],    ["What is Wonder Woman's real name?","Diana Prince"],    ["What is Batman's real name?","Bruce Wayne"]];

Next, we’ll create and initialize a variable called score to keep track of how many correct answers the player has given:

let score = 0 // initialize score

Then we’ll loop through the array using a for-of loop, assigning the variables question and answer to each key and value in the map.

The loop starts by asking the question using a prompt dialog that allows the player to enter an answer that is stored in a variable called response . We can then compare this to the actual answer stored as answer :

for(const [question,answer] of quiz){    const response = prompt(question);    if(response === answer){        alert('Correct!');        score++;    } else {        alert(`Wrong! The correct answer was ${answer}`);    }}

An if ... else block is then used to check if the answer is right or wrong. If it’s right, an alert dialog is shown saying it is correct and the score is incremented by 1, using score++ . Otherwise, if the answer is wrong, an alert dialog informs the player and also lets them know the correct answer.

When the loop has finished iterating through each question in the array, it breaks out of the block and finishes by displaying another alert dialog to inform the player the game is over and telling them how many questions they answered correctly. This uses a template literal to display the score:

// At the end of the game, report the player's scorealert(`Game Over, you scored ${score} point${score !== 1 ? 's' : ''}`);

Notice at the end of this template literal, we use the ternary operator to check if the score is not equal to 1. If this is true, the letter ‘s’ is appended to the end of the word ‘point’ to make it plural. This is a neat trick that can sometimes be overlooked, even on professional websites.

Have a go at playing the quiz in your browser by opening the index.html file. It should look like the screenshot shown below.

Quiz Ninja scores

You can also see a live example onCodePen.

Our quiz now feels much more like an actual program, and demonstrates the power of concepts such as arrays, logic and loops that we’ve learned about in this chapter.

Chapter Summary

  • Arrays are an ordered list of values
  • Multidimensional arrays are arrays that contain other arrays
  • Arrays have lots of methods that can be used to manipulate items in the array
  • Sets are new in ES6 and are ordered lists of non-duplicate values
  • Maps are new in ES6 and are ordered lists of key-value pairs
  • We can use an if and else statement to control the flow of code
  • The switch statement can be used instead of multiple if and else statements
  • while loop and do ... while loop can be used to repeat a block of code while a condition is still true
  • for loop works in a similar way to a while loop, but has a different syntax
  • for-of loop can be used to iterate over an array
  • Sets and maps are enumerable, so can also be looped over using a for-of loop

In the next chapter, we’ll be learning all about functions, a fundamental part of the JavaScript language.

Pages: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16