JavaScript 2: Loops, Arrays, and Objects#

In this lecture we start to dive into some more functionality of JavaScript. We will learn how to loop through arrays and objects and how to access and edit specific values nested within them. All of this is useful for storing and retrieving data in an organized way, a skill that will be essential to being a successful developer.

To download the demo code for this lecture:

$ dmget wb-js-2 --demo

The topics covered in this lesson will act as the foundation for most of the more advanced subjects we will cover. Please make sure you are comfortable with all of these ideas and make sure to ask for help if you’re not sure about something.

Loops#

Loops#

Loops are very useful in programming. Say we wanted to print out all even numbers between 1 and 10. We could do it this way, but it would be very repetitive:

let n = 2;
console.log(n);
n += 2;
console.log(n);
// keep going until you get to 10 ...

For Loops#

Instead we can use a for loop like this:

for (let n = 2; n <= 10; n += 2) {
  console.log(n);
}

There are three parts to a for loop:

  • Start condition. Start with n = 2.

  • Stop condition. Keep going as long as n <= 10.

  • Increment. After each time through the loop, increment n by 2.

This table traces the value of n and how it changes during the loop:

Iteration no.

n

Next value

0

\(n = 2\)

\(n + 2 = 4\)

1

\(n = 4\)

\(n + 2 = 6\)

2

\(n = 6\)

\(n + 2 = 8\)

3

\(n = 8\)

\(n + 2 = 10\)

4

\(n = 10\)

\(n + 2 = 12\)

After iteration no. 4, the next value for \(n\) is \(12\) which fails the condition n <= 10, so the loop stops. As a result, the loop will output 2, 4, 6, 8, and 10.

for loops can also count down:

for (let n = 10; n > 0; n -= 1) {
  console.log(n);
}
console.log("Blast off!");

Arrays#

Accessing/reassigning values in an array#

  • Arrays are organized by index. The first item in an array always has index 0:

    Array diagram

  • To access or reassign the item at a certain index of an array, use square brackets:

    const fruits = ['avocado', 'berry', 'cherry'];
    
    let idx = 2;
    console.log(fruits[idx]);  // 'cherry'
    
    fruits[0] = 'apple';  // reassigns first item
    console.log(fruits);  // ['apple', 'berry', 'cherry']
    

Modifying a const array#

  • Even if an array is a const, we can still modify the array by changing individual items in it.

    const fruits = ['avocado', 'berry', 'cherry'];
    fruits[0] = 'apple';  // OK! array is modified, not reassigned.
    
  • However, it won’t work to reassign the entire array:

    const fruits = ['avocado', 'berry', 'cherry'];
    fruits = ['peach', 'pineapple', 'pomegranate'];  // error!
    
  • Objects can also be modified. But primitive types (e.g. numbers, strings) cannot.

    • So a const variable with a primitive type cannot be changed at all.

Array length#

All arrays have a length property by default.

Since arrays are zero-indexed, the index of the last item in the array is actually the length minus 1.

wb-js-2-demo/arrays.js#
// Get the length of an array
// fruits = ['apple', 'berry', 'cherry'];
console.log(fruits.length); // 3

// Get the last element of an array
console.log(fruits[fruits.length - 1]); // cherry

Array iteration#

The for...of statement is a special for loop which allows you to iterate through each element in an array.

wb-js-2-demo/arrays.js#
for (const fruit of fruits) {
  console.log(fruit); // apple, berry, cherry
}

We can also use a generic for loop to iterate through an array by its indices:

wb-js-2-demo/arrays.js#
for (let i = 0; i < fruits.length; i += 1) {
  console.log(fruits[i]); // apple, berry, cherry
}

Array methods#

Arrays have methods to easily modify their contents.

  • push and pop: add or remove items from the end of an array

    // .push() adds an element to the end of an array
    fruits.push('dragonfruit');
    console.log(fruits); // ['apple', 'berry', 'cherry', 'dragonfruit']
    
    // .pop() removes the last element from an array
    const lastFruit = fruits.pop();
    console.log(lastFruit); // dragonfruit
    console.log(fruits); // ['apple', 'berry', 'cherry']
    
  • unshift and shift: add or remove items from the beginning of an array

    // .unshift() adds an element to the beginning of an array
    fruits.unshift('watermelon');
    console.log(fruits); // ['watermelon', 'apple', 'berry', 'cherry']
    
    // .shift() removes the first element from an array
    const firstFruit = fruits.shift();
    console.log(firstFruit); // watermelon
    console.log(fruits); // ['apple', 'berry', 'cherry']
    
  • slice: copy the array or get a sub-array (does NOT modify the array)

    // .slice() returns a copy of an array
    const fruits2 = fruits.slice();
    console.log(fruits2); // ['apple', 'berry', 'cherry']
    // .slice() can also take a start and end index
    const fruitsSubset = fruits.slice(1, 3);
    console.log(fruitsSubset); // ['berry', 'cherry']
    
  • splice: removes items from an array, and optionally adds new items

    // .splice() removes elements from an array and optionally adds new elements
    fruits.splice(1, 1); // remove 1 element at index 1
    console.log(fruits); // ['apple', 'cherry']
    
    fruits.splice(1, 0, 'berry'); // remove 0 elements at index 1 and add 'berry'
    console.log(fruits); // ['apple', 'berry', 'cherry']
    

Some of this also works for strings#

  • Indexing, length, and for...of also work for strings.

  • However, the array methods do not.

  • You also cannot reassign individual characters in a string.

Objects#

Accessing values with the dot operator#

Previously, you learned how to use the dot operator (.) to access the values of properties on an object.

wb-js-2-demo/objects.js#
const fruitsInventory = {
  apple: 2,
  berry: 4,
  cherry: 6,
};

console.log(fruitsInventory.apple); // 2

You can also access the values of properties with bracket notation.

Accessing values with bracket notation#

  • The most common use-case for bracket notation is when the key name is stored in a variable:

    const fruitName = 'apple';
    console.log(fruitsInventory[fruitName]); // 2
    
  • Note that fruitsInventory.fruitName will NOT work here!

    • This will look for a key called 'fruitName' — it won’t treat fruitName as a variable.

You must also use bracket notation instead of dot notation if the key name contains a space:

wb-js-2-demo/objects.js#
const weirdFruits = {
  'star apple': 2,
  kumquat: 4,
  loganberry: 6,
};

console.log(weirdFruits['star apple']); // 2

Adding/updating values#

  • The same syntax is used to add/update values in an object.

  • If the key exists, the value is updated.

    const fruitsInventory = {apple: 2, berry: 4, cherry: 6};
    
    fruitsInventory.apple = 3;
    console.log(fruitsInventory); // { apple: 3, berry: 4, cherry: 6}
    
  • If the key doesn’t exist, a new key/value pair is added.

    fruitsInventory.dragonfruit = 8;
    console.log(fruitsInventory); // { apple: 3, berry: 4, cherry: 6, dragonfruit: 8 }
    

You can also add/update objects with bracket notation:

const weirdFruits = {'star apple': 2, kumquat: 4, loganberry: 6};

fruitsInventory['kumquat'] = 5;
// value of 'kumquat' updated to 5

weirdFruits['prickly pear'] = 7;
console.log(weirdFruits);
// {'star apple': 2, kumquat: 5, loganberry: 6, 'prickly pear': 7}

Object syntax flowchart#

Assigning a value

digraph { rankdir="TB"; expr [ fontname="monospace" label="fruits.apple = 3" shape="box" color="transparent" ]; condition [ label=<<b>does the 'apple'<br></br>key exist?</b>> shape="none" fillcolor="#cccccc" color="transparent" fontcolor="black" ]; { rank="same"; yes [label="update value\nto 3"]; no [label="add 'apple' key\nwith value 3"]; } expr -> condition [arrowhead="none"]; condition -> yes [label="yes"]; condition -> no [label="no"]; }

Accessing a value

digraph { rankdir="TB"; expr [ fontname="monospace" label="fruits.apple" shape=box color=transparent ]; condition [ label=<<b>does the 'apple'<br></br>key exist?</b>> shape=none fillcolor="#cccccc" color="transparent" fontcolor="black" ]; { rank=same; yes [label="return value\nstored at\n'apple' key"]; no [label="return\nundefined"]; } expr -> condition [arrowhead="none"]; condition -> yes [label="yes" lp=5]; condition -> no [label="no"]; }

The delete operator#

The delete operator removes a key-value pair from an object.

const fruitsInventory = { apple: 3, berry: 4, cherry: 6, dragonfruit: 8 };

delete fruitsInventory.dragonfruit;
console.log(fruitsInventory);  // { apple: 3, berry: 4, cherry: 6}

Looping through objects#

You can loop through all the keys of an object with a for...in loop:

for (const fruit in fruitsInventory) {
  console.log(fruit, fruitsInventory[fruit]);
}
Output:
apple 3
berry 4
cherry 6

for...of does not work with objects!

Values, entries, and keys#

  • You can get just the values of an object with Object.values():

    console.log(Object.values(fruitsInventory));  // [3, 4, 6]
    
  • Object.entries() gets you both the key and the value:

    for (const [fruit, num] of Object.entries(fruitsInventory)) {
      console.log(fruit, num);
    }
    
  • There is also Object.keys() which gets you an array of keys.

Caution: Don’t use for...in with arrays#

const fruits = ['apple', 'berry', 'cherry'];

for (const f in fruits) {
  console.log(f);
}
// Output:
// 0
// 1
// 2

If you try to loop through an array with for...in, you’ll actually get all the indices.

Nested data structures#

Nested data structures#

Arrays can be nested inside other arrays:

wb-js-2-demo/nested.js#
const tictactoe = [
  ['X', 'O', 'X'],
  ['O', 'X', 'O'],
  ['?', 'O', 'X'],
];

// Get the value in the 3rd row, 1st column ('?')
console.log(tictactoe[2][0]);

// Print out all values
for (const row of tictactoe) {
  for (const cell of row) {
    console.log(cell);
  }
}

Arrays and objects can be nested inside each other:

wb-js-2-demo/nested.js#
const fruits = [
  {
    name: 'apple',
    genus: 'Malus',
    colors: ['red', 'yellow', 'green'],
  },
  {
    name: 'berry',
    genus: 'Vaccinium',
    colors: ['red', 'blue'],
  },
  {
    name: 'cherry',
    genus: 'Prunus',
    colors: ['red'],
  },
];

console.log(type(fruits)); // Array
console.log(type(fruits[0])); // Object
console.log(fruits[0].colors); // ['red', 'yellow', 'green']
console.log(type(fruits[0].colors[1])); // 'yellow'

The ternary expression#

The ternary expression#

The ternary expression is a commonly used syntactic shortcut for if...else statements. Instead of this:

const isRaining = true;

let outerwear;
if (isRaining) {
  outerwear = 'raincoat';
} else {
  outerwear = 'cardigan';
}

We can write this, using a ternary expression:

wb-js-2-demo/ternary.js#
const isRaining = true;

const outerwear = isRaining ? 'raincoat' : 'cardigan';
console.log(outerwear); // raincoat

The syntax for a ternary looks like this:

condition ? what to do if true : what to do if false

However, it’s considered a bad practice to use ternaries of an assignment expression because they can make your code less readable and maintainable.

When to not use ternaries

For example, this is perfectly valid code:

const n = 1;
n < 100 ? console.log('small number') : console.log('big number');

…but you should probably refactor it into a normal if...else. If the logic in your code ever changes, it’s a lot easier to add conditions to an if...else than a ternary expression.