We are now about to leave the realm of pure expressions.
Whereas expressions always evaluate to a value, there are constructs in Javascript that exist to control the flow of our program but which do not themselves produce values.
The “flow” of our program is just a way of talking about what happens when.
The normal flow of control within a function is each statement (roughly speaking, a line) executing in order.
console.log('Hello.');
console.log('How are you doing?');
When this code runs, the first line runs first, printing ‘Hello.’ to the console and then the second line runs, printing ‘How are you doing?’.
Calling and returning from a function.
Conditional execution: either run some code or don’t based on some condition.
Looping: run some code repeatedly, while some condition holds.
When we call a function we leave the normal flow to jump to the code in the function we are calling.
The function’s code is executed and then when it returns, we come back to where the function was called and resume normal flow.
return
You’ve already seen return
as it is a key part of defining functions.
const double => (x) => {
return x * 2;
};
The return
causes the function it is in to return the value of the following expression to whatever code called the function.
double
const x = double(16);
The value of that call is whatever is return
’d from the function when called with the argument 16.
That value is then assigned to the variable x
.
The line return x * 2
is not an expression because it does not evaluate to a value. For instance we can’t say:
let foo = 10 + (return x * 2); // ☠️☠️☠️
Javascript will complain, probably something like ‘Unexpected token return’ which is just a way of saying: this doesn’t belong here.
return
statementInstead we describe it as a “return statement”.
As in, “Your function needs a return statement.”
The return
is a control flow statement because it changes the flow of what code executes next.
const foo = () => {
return 10;
console.log('hello'); // this line never runs
};
if
The main way to conditionally execute some code is with an if
statement.
if (x > 10) {
console.log('x is really big!');
}
The code inside the {}
s only runs if the expression inside the ()
s evaluates to true.
if
if (
condition) {
body }
else
Sometimes we want to choose between two options:
if (x > 10) {
console.log('x is really big!');
} else {
console.log('x is small.');
}
The code in the {}
s after the else
is called the else clause or else branch and it runs if the condition is false.
If we wanted to make a three-way choice, we could write this:
if (x > 10) {
console.log('x is really big!');
} else {
// In here we know x is <= 10
if (x > 5) {
console.log('x is kinda medium');
} else {
console.log('x is small.');
}
}
The outer if
has two branches: one where x
is greater than 10 and one for when it’s not, i.e. when it’s less than or equal to 10.
The second if
, inside the else
branch of the first if
, branches into an x
> 5
path and an everything else path.
Normally we’d write that like this
if (x > 10) {
console.log('x is really big!');
} else if (x > 5) {
console.log('x is kinda medium');
} else {
console.log('x is small.');
}
Same logic, just formatted slightly differently.
Loops provide a way to execute some code repeatedly, usually until some condition is met.
while
for
Most of the variables we’ve been dealing with (function arguments or variables defined with const
) have been used as names for unchanging values.
However, for a loop to only repeat until some condition is met, it must be possible for conditions to change.
That usually means that some variable’s value is going to have to change.
let
As was mentioned briefly in the Variables slides, variables declared with let
rather than const
can be assigned new values after they are first defined.
let x = 10;
// some time later
x = 20;
We say that second line assigns the value 20 to x
.
Any expression can go on the right-hand side of the =
.
In particular …
x = x + 1
That looks funny as math because no number is equal to itself plus one.
But in Javascript =
means, evaluate the expression on the right hand side of the =
and then assign that value to the variable on the left hand side.
x + 1
will evaluate to the number one greater than the current value of x
and then that new value will be assigned to x
.
x++
Because it is so common to want to increment a variable by one, there is a extra-concise shortcut for doing that, shown above.
It is frequently used in loops.
while
while
loops do what the name suggests: loop until some condition is true.
let x = 0;
while (x < 100) {
console.log(x);
x++;
}
You can read this as, “While x is less than 100 console.log it and then add 1 to it”.
The expression in the ()
s is evaluated and if it evaluates to true then the body of the while
—the code between the {}
s—is executed.
After executing the body, control jumps to the top of the loop, checking the condition again and executing the body as long as the condition is true.
Then normal flow resumes immediately after the loop.
for
for
loops capture a common pattern of looping.
In fact the while
loop from two slides ago is an example of that pattern.
First we initialize some variable: let x = 0;
Then we loop while some expression involving that variable is true: x < 100
In the loop body we do whatever and then update the variable: x++
for
loop equivalentThis for
loop is equivalent to the earlier while
loop.
for (let x = 0; x < 100; x++) {
// do whatever.
}
for
loop structurefor (
initializer;
condition;
updater) {
body; }
The initializer runs once at the start of the loop.
It usually defines a variable, called the loop variable, that will control the loop.
The condition is evaluated at the start of each time through the loop.
When it evaluates to false, the loop immediately ends.
Otherwise the body of the loop executes once before possibly looping again.
The condition almost always involves the loop variable.
The updater runs immediately after the body of the loop and before the condition is checked again.
It almost always updates the loop variable in some way that potentially changes the result of evaluating the condition.
The code that runs each iteration of the loop.
Often uses the loop variable but should not update it.
(Leave that to the updater.)
for (let i = 0; i < s.length; i++) {
// Do something with s[i], e.g.
console.log(s[i])
}
You will eventually be able to write this loop in your sleep.
Let’s take it apart.
let i = 0
Declare the loop variable and set it to zero.
In loops like this the loop variable is almost always called i
, short for “index” since that’s how it’s going to be used.
i < s.length
We want to execute the loop body once for each valid index in the string.
Which we know go from 0 (where we initialized i
) up to but not including s.length
.
i++
Just increment (i.e. add one to) i
so it takes on all the values from 0
up to s.length
.
The last update will set i
to s.length
which will cause the condition, i <
s.length
, to become false, ending the loop.
console.log(s[i]);
In this case we’re just printing out each character.
Because each time we execute the body i
will have a different value, s[i]
will extract a different character from s
.
If we need to work in two dimensions we may need to nest loops.
for (let i = 0; i < n; i++) {
for (let j = 0; j < m; j++) {
// code that refers to i and j
}
}
The body of the inner loop will execute n * m
times.
The two loop variables need to have different names or the inner loop’s variable will hide the outer loop variable and we won’t be able to refer to it.
The following slides contain some extra information you don’t really need to worry about.
But if you’re interested in knowing the whole truth you might want to read them.
Otherwise, feel free to skip to the last slide.
Some extra details about if
if
statementThe actual rules of Javascript allow either a single statement or a block contained in {}
s after the condition of an if
or after an else
in an if
/else
.
So this is legal:
if (x > 10)
console.log('x is really big!');
and is equivalent to this version with braces:
if (x > 10) {
console.log('x is really big!');
}
We should always use braces.
Here’s why.
if (x > 10)
x = 10;
Perfectly legal. Clamps x
to be no greater than 10.
Then suppose we want to add some output, so we add a line like this:
if (x > 10)
console.log('Clamping x to 10');
x = 10;
Do you see the problem?
Despite the indentation that code is equivalent to this:
if (x > 10) {
console.log('Clamping x to 10');
}
x = 10;
I.e. x now always gets set to 10 because the assignment isn’t in the body of the if
any more.
If we had written it like this from he beginning
if (x > 10) {
x = 10;
}
Then when we added the line it would turn into this:
if (x > 10) {
console.log('Clamping x to 10');
x = 10;
}
And would work the way we intended.
Don’t make it harder.
We should adopt coding practices that make it easier to write correct code.
Every little bit helps.
If you were really, really paying attention you might notice that there is a place where I advised leaving out the braces.
if (x > 10) {
console.log('x is really big!');
} else if (x > 5) {
console.log('x is kinda medium');
} else {
console.log('x is small.');
}
if (x > 10) {
console.log('x is really big!');
} else
if (x > 5) {
console.log('x is kinda medium');
} else {
console.log('x is small.');
}
The second if
/else
counts as a single statement and thus the whole body of the first else
branch.
Two kinds of functions with no return
statement.
In the Functions slides you saw a shorthand form:
const double = (n) => n * 2;
Which is a more concise way of writing:
const double = (n) => {
return n * 2;
}
It just saves us from having to write the {}
s and the return
when the body of the function is a single expression whose value we want to return.
This function also has no return
const drawTriangle = (x1, y1, x2, y2, x3, y3) => {
drawLine(x1, y1, x2, y2, 'blue');
drawLine(x2, y2, x3, y3, 'blue');
drawLine(x3, y3, x1, y1, 'blue');
};
When does it return?
And what value does it return to its caller?
This is an example of a function that we call for the side effects it has (drawing lines on the screen), not for the value it computes.
It returns when the normal flow gets to the end of the function body.
And it returns the special value undefined
.
Which doesn’t usually matter because the code that called this function probably isn’t doing anything with the return value anyway.
const drawTriangle = (x1, y1, x2, y2, x3, y3) => {
drawLine(x1, y1, x2, y2, 'blue');
drawLine(x2, y2, x3, y3, 'blue');
drawLine(x3, y3, x1, y1, 'blue');
return undefined;
};
But it may be the special value undefined
which isn’t useful for much of anything.
And there may be no explicit return
in the code of the function.