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
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 withvar
.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.
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.
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`) }
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.
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') }
column
when the value of direction
is 'East'
? What happens when the breaks are present (as they should be)?
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]
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 }
The operators &&
(“and-also”) and ||
(“or-else”) are short-circuit operators:
x && y
: First evaluate x
.
If it’s falsy then return x
without evaluating y, otherwise evaluate and return y
.
x || y
: First evaluate x
.
If it’s truthy then return x
without evaluating y, otherwise evaluate and return y
.
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:
x && y
: We need both $x$ and $y$, so if $x$ is bad, forget the whole thing, otherwise make sure $y$ works too.
x || y
: We need just one of $x$ and $y$, so if $x$ is good, take it, otherwise try $y$.
In practice:
&&
when it only makes sense to continue if the first part worked.
||
to supply a “Plan B” or a default value.
swim() && bike() && run() openDoor() || goThroughWindow() car.color = favoriteColor || "black"
Iteration means doing things repeatedly, either while some condition is true (indefinite iteration), or for each element in a range or collection (definite iteration).
Sometimes we have some kind of collection we want to iterate through, such as:
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
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
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) } }
The flexible for-loop form shows up in linked lists:
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
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
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)!
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).
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}`) }
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. WhileIf 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
.
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 break
s 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)
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}`)
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”.
We’ve covered: