JavaScript Statements

Expressions are evaluted to produce values. Statements are executed to produce actions. What are the statements of JavaScript? When do we use them?

Declaration Statement

Also called the variable statement or binding statement. Brings a new variable into existence, with an initial value. (If no initial value is explicit, it will be undefined.)

let count = 0
const tau = 2 * Math.PI
let dogs = ["Sparky", "Spot", "Spike"]
let response                                        // undefined
const malta = {latitude: 35.8, longitude: 14.6}
let finished = false
let winner = null

You can define multiple variables in one statement, too.

let start = 0, finish = 72, input, output = []

You can define variables by destructuring (you can think of this as a kind of pattern matching):

let [x, y] = [1, 2]                     // x=1 and y=2
let [first, , last] = dogs              // first='Sparky' last='Spike' (see above)
let {latitude, longitude} = malta       // latitude=35.8, longitude=14.6 (see above)

let order = {
  givenName: 'Jane',
  surname: 'Xu',
  address: {street: 'First', number: 100, city: 'Eureka', state: 'CA'},
  sku: 'C78-X049322-P1'
}
let {surname: s, address: {number: q}} = order    // s='Xu', q=100

Expression Statement

Any expression can be a statement. The most important, and by far the most common such statements, are assignments and function calls.

// First some declrations
let x = 2              // Declares x, initializing it to 2.
let y                  // Declares y, without an explicit initial value.

// Now, the Expressions statements
console.log(x)         // Prints 2.
console.log(10 * x)    // Prints 20.
console.log(y)         // Prints undefined.
y = x * 5              // Assigns 10 to y, because x is still 2.

Assignment is a copy, not a share

let y = 10
let z = y              // Declares z, initializing it to 10.
y = "dog"              // Assigns "dog" to y, overwriting the old value 10.
console.log(y + z)     // Prints "dog10", because z is still 10.

A variable introduced with let can be reassigned, but not a variable introduced with const:

> let x = 5
> x = 2
2
> x
2
> const y = 100
> y = 7
TypeError: Assignment to constant variable.
> y
100
You can also introduce variables with var.

But don’t.

Destructuring also works with assignment. This makes it pretty easy to “swap” variables:

let x = 3
let y = 5
;[x, y] = [y, x]     // Now x=5 and y=3
Careful all you non-semicolon people

If you start a statement with a left-bracket, you should usually put a semicolon before it, otherwise it gets slammed into the statement before it and will look like you are accessing a property!

Some other assignment operators:

let x = 5
x += 30          // Same as x = x + 30, x now 35.
x -= 2           // Same as x = x - 2, x now 33.
x *= -4          // Same as x = x * -4, x now -132.

Here’s a complete script:

/*
 * This is the first attempt at a script to find the number of
 * dollars and cents for a given number of pennies. It has some
 * problems we will fix soon.
 */
const pennies = prompt('Enter a number of (U.S.) pennies')
const dollars = Math.floor(pennies / 100)
const cents = pennies % 100
alert(`That's ${dollars} dollars and ${cents} cents`)

But wait, the results don’t always look good (try them), and for negative numbers, we get the wrong result. We’ll fix it in a second.

Conditionals

A conditional is a construct that produces or does different things under different conditions. There are lots of ways to do conditional computation. The one that comes to mind first is the if statement, but it is often overused. The other ways are often better.

If Statement

The if statement has one if-part, zero or more else-if-parts, and zero or one else-part.

const score = Math.round(Math.random() * 100)
if (score >= 90) {
  grade = "A"
} else if (score >= 80) {
  grade = "B"
} else if (score >= 70) {
  grade = "C"
} else if (score >= 60) {
  grade = "D"
} else {
  grade = "F"
}
console.log(`${score} is a ${grade}`)

Now we can fix the dollars and cents script:

/*
 * This is the second attempt at a script to find the number of
 * dollars and cents for a given number of pennies.
 */
let pennies = Number(prompt("Enter a number of cents"))
if (isNaN(pennies)) {
  alert("That's not a number")
} else if (pennies < 0) {
  alert("That number is too small")
} else if (pennies > Number.MAX_SAFE_INTEGER) {
  alert("That number is too large")
} else if (!Number.isInteger(pennies)) {
  alert("I can only handle whole numbers")
} else {
  let dollars = Math.floor(pennies / 100)
  let cents = pennies % 100
  alert(`That's ${dollars} dollars and ${cents} cents`)
}

Conditional Expression

You don’t always want the if statement. Sometimes this expression is fine:

hemisphere = (latitude >= 0) ? 'north' : 'south'

In general, x?y:z produces y if x is truthy and z otherwise.

The _?_:_ expression goes by many names. Some people call it a “conditional expression,” and others a “ternary expression.” The operator itself goes by the name “conditional operator” or “ternary operator.” The term “ternary” comes form the fact that there are three operands, as opposed to a binary operator (which has two) or a unary operator, which has one.

You really shouldn’t call it THE ternary operator, though! Because even though there’s only one three-operand operator in JavaScript, there could, in principle, be others.

Switch Statement

In the switch statement, an expression is evaluated then compared to each case in succession using === until there is a match, then execution continues from that point. Use break (or return or throw to prevent "falling through").

switch (direction.toLowerCase()) {
  case 'north': row -= 1; break
  case 'south': row += 1; break
  case 'east': column += 1; break
  case 'west': column -= 1; break
  default: console.log('Illegal direction')
}
Exercise: Without the breaks, what happes to the value of column when the value of direction is 'East'? What happens when the breaks are present (as they should be)?

Lookups

When you need to get a value from an association (such as states to captials), then if statements, switch statements, and conditional expressions all have repeated code. Instead, just store the association in an object:

const CAPITALS = {
  'Baden-Württemberg': 'Stuttgart',
  'Bayern': 'München',
  'Berlin': 'Berlin',
  'Brandenburg': 'Potsdam',
  'Bremen': 'Bremen',
  'Hamburg': 'Hamburg',
  'Hessen': 'Wiesbaden',
  'Mecklenburg-Vorpommern': 'Schwerin',
  'Niedersachsen': 'Hannover',
  'Nordrhein-Westfalen': 'Düsseldorf',
  'Rheinland-Pfalz': 'Mainz',
  'Saarland': 'Saarbrücken',
  'Sachsen': 'Dresden',
  'Sachsen-Anhalt': 'Magdeburg',
  'Schleswig-Holstein': 'Kiel',
  'Thüringen': 'Erfurt'
}

Then it’s just:

capital = CAPITALS[state]
Exercise: Why would computing the capital of a state using an (a) if-statement, (b) conditional expression, or (c) switch statement be awful?

One more example. To find the number associated with a given letter on a phone keypad:

const LETTER_TO_NUMBER = { A: 2, B: 2, C: 2, D: 3, E: 3, F: 3,
  G: 4, H: 4, I: 4, J: 5, K: 5, L: 5, M: 6, N: 6, O: 6, P: 7,
  Q: 7, R: 7, S: 7, T: 8, U: 8, V: 8, W: 9, X: 9, Y: 9, Z: 9
}

Short-Circuiting

The operators && (“and-also”) and || (“or-else”) are short-circuit operators:

This means, for example:

console.log(27 && 52)   // Prints 52
console.log(0 && 52)    // Prints 0
console.log(27 || 52)   // Prints 27
console.log(0 || 52)    // Prints 52

The rationale is:

In practice:

swim() && bike() && run()
openDoor() || goThroughWindow()
car.color = favoriteColor || "black"

Iteration

Iteration means doing things repeatedly, either while some condition is true (indefinite iteration), or for each element in a range or collection (definite iteration).

Definite Iteration

Sometimes we have some kind of collection we want to iterate through, such as:

For-of

The most normal form of JavaScript definition iteration is for-OF! (Careful: if you come from Python-land, you will be used to for-IN, but JavaScript’s for-in is different. Here is the normal iteration through the array values:

let lights = ['red', 'amber', 'green']
for (let light of lights) {
    console.log(light)
}
// red
// amber
// green

Here’s a more useful example:

let words = ['as', 'far', 'as', 'i', 'know']
let acronym = ''
for (let word of words) {
  acronym += word[0].toUpperCase()
}
console.log(acronym)
// AFAIK

It is common to see for-statements with nested if-statements, for example:

let scores = [7, 3, 0, 0, 9, -5, 2, 1, 0, 1, 7]
let countOfZeros = 0
for (let score of scores) {
  if (score === 0) {
    countOfZeros += 1
  }
}
console.log(countOfZeros)
// 3

For-of works not only for array values but also for the characters of a string:

> for (let c of 'Hello') {console.log(c.toUpperCase());}
// H
// E
// L
// L
// O
const LETTER_TO_NUMBER = { A: 2, B: 2, C: 2, D: 3, E: 3, F: 3,
  G: 4, H: 4, I: 4, J: 5, K: 5, L: 5, M: 6, N: 6, O: 6, P: 7,
  Q: 7, R: 7, S: 7, T: 8, U: 8, V: 8, W: 9, X: 9, Y: 9, Z: 9
}
const phoneText = prompt('Enter a phone number (letters permitted)')
let phoneNumber = ''
for (let c of phoneText) {
  if (/\d/.test(c)) {
    phoneNumber += c
  } else if (c in LETTER_TO_NUMBER) {
    phoneNumber += LETTER_TO_NUMBER[c]
  }
}
console.log(`The phone number is: ${phoneNumber}`)

If you need to capture the array indices while you iterate, you need to use .entries on your array:

const tasks = ['Wake up', 'Brush teeth', 'Play', 'Sleep']
for (let [order, task] of tasks.entries()) {
  console.log(`${task} is at index ${order}`)
}
// Wake up is at index 0
// Brush teeth is at index 1
// Play is at index 2
// Sleep is at index 3

Use Object.entries with regular objects:

const countries = {
  Netherlands: 'Amsterdam',
  Aruba: 'Oranjestad',
  Curaçao: 'Willemstad',
  'Sint Maarten': 'Philipsburg'
}
for (let [country, capital] of Object.entries(countries)) {
    console.log(`The capital of ${country} is ${capital}`)
}
// The capital of Netherlands is Amsterdam
// The capital of Aruba is Oranjestad
// The capital of Curaçao is Willemstad
// The capital of Sint Maarten is Philipsburg

For-in

JavaScript has for...in but you won’t use it often, if ever. It is kind of confusing. It only goes through the keys of an object, not it key-value pairs:

const countries = {
  Netherlands: 'Amsterdam',
  Aruba: 'Oranjestad',
  Curaçao: 'Willemstad',
  'Sint Maarten': 'Philipsburg'
}
for (let country in countries) {
  console.log(country)
}
// Netherlands
// Aruba
// Curaçao
// Sint Maarten

DON’T fall into the trap of using for-in for arrays. Arrays are objects, but remember what the keys are!

let colors = ['red', 'amber', 'green']
for (let c in colors) {
  console.log(c)
}
// 0
// 1
// 2

Iterating through numeric ranges

There is a form of the for-statement that is super-flexible and cool:

for (initialization ; whenToStop ; whatToDoAtEndOfEachIteration) {
  body
}

It is often used to iterate through numeric ranges. Here’s a contrived example, through the range 4..20, stepping by 2 (4, 6, 8, 10, 12, 14, 16, 18, 20):

for (let number = 4; number <= 20; number += 2) {
  console.log(`${number} is even`)
}

Something a little more useful, a pretty picture of nested squares in p5.js:

function setup() {
  createCanvas(320, 320)
  rectMode(CENTER)
  noFill()
  for (let side = 10; side <= 300; side += 10) {
    rect(width/2, height/2, side, side)
  }
}

Linked Lists

The flexible for-loop form shows up in linked lists:

linkedlist.png

let scores = {score: 29, next: {score: 99, next: {score: 47, next: null}}}
let total = 0
for (let node = scores; node != null; node = node.next) {
  total += node.score
}
console.log(total)
// 175

Nested For-Loops

If you ever need to work with “rows and columns” or do something over all pairs of items, you’ll likely put one for stament inside another, as in this simple multiplication table example:

const SIZE = 20
let table = "<table>"
for (let i = 1; i <= SIZE; i += 1) {
  table += "<tr>"
  for (let j = 1; j <= SIZE; j += 1) {
    table += `<td>${i * j}</td>`
  }
  table += "</tr>"
}
table += "</table>"
document.body.innerHTML = table

Definite iteration with Array methods

There are many computational patterns on arrays that are so common that standard array operations exist so that you don’t have to use a for-statement. The most common is map, which applies an operation to each element of an array, returning a new array with the “mapped” values:

const words = ['as', 'far', 'as', 'i', 'know']
console.log(words.map(w => w[0].toUpperCase()).join(''))
// AFAIK

We’ll cover the details later when we go over functions. For now, get lots of practice with for-loops (as they are often called)!

Indefinite Iteration

If you don’t have a collection or range to iterate through, but you simply need to keep doing something while (or until) something happens (an ending condition is reached).

While Statement

Let’s simulate a game in which a player has to get from level 5 to level 90. At each step the player can jump as high as 5 steps or fall as many as 3 steps.

let level = 5
while (level < 90) {
  level += Math.floor(Math.random() * 9 - 3)
  console.log(`Now at level ${level}`)
}

Do-While Statement

You can put the test at the end if you know that you have to run the iteration at least once:

let level = 5
do {
  level += Math.floor(Math.random() * 9 - 3)
  console.log(`Now at level ${level}`)
} while (level <= 90)
For vs. While

If you know how many times you need to iterate, i.e., you have a collection or range to iterate through, use for.

If you do not know how many times you need to iterate, i.e., you just need to go forever or until some condition occurs, use while.

Disruption

Break Statement

A break immediately terminates a loop. It is commonly used to make code less clunky. Let’s add to the example previous section. If the player reaches or goes below level 0, the player will die:

let level = 5
while (level < 90) {
  level += Math.floor(Math.random() * 9 - 3)
  console.log(`Now at level ${level}`)
  if (level <= 0) {
    console.log('Player died!')
    break
  }
}

We also see breaks together with the while (true) construct:

const secret = 43
let message = 'Welcome'
while (true) {
  let guess = Number(prompt(`${message}. Guess a number:`))
  if (isNaN(guess)) {
    message = 'Not a number'
  } else if (guess < secret) {
    message = 'Too low'
  } else if (guess > secret) {
    message = 'Too high'
  } else {
    alert('YOU WIN!')
    break
  }
}

You can even use break in a for statement, even though that is definite iteration. The idea here is that you have a collection to iterate through, but you might just be looking for something in the collection and want to leave once you found something. (Of course you could use a while statement for this sort of thing, but it gets ugly.)

// Find the index position of the first even number in an array of.
// numbers. This is just an example; in practice you would use the
//  built-in findIndex operation.
for (let [position, value] of numbers.entries()) {
  if (value % 2 === 0) {
    console.log(`Even number found at index ${position}`)
    break
  }
}

Loops can be labeled in case you need to break out of an “outer” loop:

const picks = {
  Alice: [4, 52, 9, 1, 30, 2],
  Boris: [14, 9, 3, 6, 22, 40],
  Chi: [51, 53, 48, 21, 17, 8],
  Dinh: [1, 2, 3, 4, 5, 6],
}

let found = false
Search: for (let person in picks) {
  let choices = picks[person]
  for (let choice of choices) {
    if (choice === 53) {
      found = true
      break Search
    }
  }
}
console.log(found)

Continue Statement

A continue immediately finishes the current iteration of a loop. It says "forget about this one, go on to the next one."

// Computes the sum of all positive values in array.
let sum = 0
for (let x of array) {
  if (x <= 0) {
    continue              // Skip nonpositives.
  }
  sum += x                // Accumulate positives.
}
console.log(`Sum of positives is ${sum}`)

Errors

If an operation makes no sense, an error should be thrown. JavaScript has a few built in error types.

> let a = [10, 20, 30];
> a.length = -5;
RangeError: Invalid array length
> a = null;
> a[3]
TypeError: Cannot read property '3' of null
> @#@*($#*(;
SyntaxError: Invalid or unexpected token

You can thow errors yourself:

console.log('Welcome to my script')
throw 'Ha ha ha'
console.log('You will never see this message')

To catch errors, use a catch clause like so:

try {
  // This is a contrived example that just illustrates a point
  console.log('Welcome to my script')
  throw 'Ha ha ha'
  console.log('You will never see this message')
} catch (e) {
  console.log(`Caught: ${e}`)
}

You might wish to throw an object that has details about the error (so anyone catching it can deal with the error in the most appropriate way):

throw {reason: 'class full', limit: 20, date: '2012-12-22'}

Or throw an Error object:

throw new Error("Sorry, I don't accept words like that.")

Have you seen the operator new before? That’s right, Error and friends are classes. (Yeah, yeah, I know, if you are a JavaScript expert you might say not-really-classes, but it’s fine to say that.)

Did you know?

Errors are also called “exceptions”.

Summary

We’ve covered:

  • Statements that declare variables and constants
  • Assignment statements and function call statements
  • If-statements and other ways to do conditional computation
  • The many ways to write loops
  • Control-flow statements such and break and continue
  • Throwing and catching errors