What is Control Flow?
Site: | Saylor Academy |
Course: | PRDV401: Introduction to JavaScript I |
Book: | What is Control Flow? |
Printed by: | Guest user |
Date: | Saturday, October 12, 2024, 1:26 AM |
Description
A fundamental skill in programming is controlling how a program executes. The control of program execution is categorized using three control structures. They are "sequential", "selection", and "repetition". So far, you've written straightforward code that uses sequential JavaScript statements that execute one after another. More complex decision-making programs use selection and repetition. Programs also implement different types of looping structures, such as looping until some condition is met (sentinel) or looping a specific number of times (counter control). As you read through this section, you should notice that control structures in JavaScript use "reserved keywords" (if, else, switch).
Control flow
When your program contains more than one statement, the statements are executed as if they are a story, from top to bottom. This example program has two statements. The first one asks the user for a number, and the second, which is executed after the first, shows the square of that number.let theNumber = Number(prompt("Pick a number"));
console.log("Your number is the square root of " +
theNumber * theNumber);
The function Number
converts a value to a number. We need that conversion because
the result of prompt
is a string value, and we want a number. There are similar functions called String
and Boolean
that convert values to those types.
Here is the rather trivial schematic representation of straight-line control flow:
Source: Marijn Haverbeke, https://eloquentjavascript.net/02_program_structure.html
This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 License.
Conditional execution
Not all programs are straight roads. We may, for example, want to create a branching road, where the program takes the proper branch based on the situation at hand. This is called conditional execution.
Conditional execution is created with the if
keyword in JavaScript. In the simple case, we want some code
to be executed if, and only if, a certain condition holds. We might, for example, want to show the square of the input only if the input is actually a number.
let theNumber = Number(prompt("Pick a number")); if (!Number.isNaN(theNumber)) { console.log("Your number is the square root of " + theNumber * theNumber); }
With this modification, if you enter "parrot", no output is shown.
The if
keyword executes or skips a statement depending on the value of a Boolean expression. The deciding
expression is written after the keyword, between parentheses, followed by the statement to execute.
The Number.isNaN
function is a standard JavaScript function that returns true
only if the
argument it is given is NaN
. The Number
function happens to return NaN
when you give it a string that doesn't represent a valid number. Thus, the condition translates to "unless theNumber
is not-a-number,
do this".
The statement after the if
is wrapped in braces ({
and }
) in this example. The
braces can be used to group any number of statements into a single statement, called a block. You could also have omitted them in this case, since they hold only a single statement, but to avoid having to think about whether they are needed,
most JavaScript programmers use them in every wrapped statement like this. We'll mostly follow that convention in this book, except for the occasional one-liner.
if (1 + 1 == 2) console.log("It's true"); // → It's true
You often won't just have code that executes when a condition holds true, but also code that handles the other case.
This alternate path is represented by the second arrow in the diagram. You can use the else
keyword, together with if
, to create two separate, alternative execution paths.
let theNumber = Number(prompt("Pick a number")); if (!Number.isNaN(theNumber)) { console.log("Your number is the square root of " + theNumber * theNumber); } else { console.log("Hey. Why didn't you give me a number?"); }
If you have more than two paths to choose from, you can "chain" multiple if
/else
pairs together.
Here's an example:
let num = Number(prompt("Pick a number")); if (num < 10) { console.log("Small"); } else if (num < 100) { console.log("Medium"); } else { console.log("Large"); }
The program will first check whether num
is less than 10. If it is, it chooses that branch, shows "Small"
,
and is done. If it isn't, it takes the else
branch, which itself contains a second if
. If the second condition (< 100
) holds, that means the number is at least 10 but below 100, and "Medium"
is
shown. If it doesn't, the second and last else
branch is chosen.
The schema for this program looks something like this:
While and do loops
Consider a program that outputs all even numbers from 0 to 12. One way to write this is as follows:
console.log(0); console.log(2); console.log(4); console.log(6); console.log(8); console.log(10); console.log(12);
That works, but the idea of writing a program is to make something less work, not more. If we needed all even numbers less than 1,000, this approach would be unworkable. What we need is a way to run a piece of code multiple times. This form of control flow is called a loop.
Looping control flow allows us to go back to some point in the program where we were before and repeat it with our current program state. If we combine this with a binding that counts, we can do something like this:
let number = 0; while (number <= 12) { console.log(number); number = number + 2; } // → 0 // → 2 // … etcetera
A statement starting with the keyword while
creates a loop. The word while
is followed by
an expression in parentheses and then a statement, much like if
. The loop keeps entering that statement as long as the expression produces a value that gives true
when converted to Boolean.
The number
binding demonstrates the way a binding can track the progress of a program. Every time the
loop repeats, number
gets a value that is 2 more than its previous value. At the beginning of every repetition, it is compared with the number 12 to decide whether the program's work is finished.
As an example that actually does something useful, we can now write a program that calculates and shows the value of 2 10 (2 to the 10th power). We use two bindings: one to keep track of our result and one to count how often we have multiplied this result by 2. The loop tests whether the second binding has reached 10 yet and, if not, updates both bindings.
let result = 1; let counter = 0; while (counter < 10) { result = result * 2; counter = counter + 1; } console.log(result); // → 1024
The counter could also have started at 1
and checked for <= 10
, it is a good idea to get used to counting from 0.
A do
loop is a control structure similar to a while
loop. It differs only on one point: a
do
loop always executes its body at least once, and it starts testing whether it should stop only after that first execution. To reflect this, the test appears after the body of the loop.
let yourName; do { yourName = prompt("Who are you?"); } while (!yourName); console.log(yourName);
This program will force you to enter a name. It will ask again and again until it gets something that is not an empty
string. Applying the !
operator will convert a value to Boolean type before negating it, and all strings except ""
convert to true
. This means the loop continues going round until you provide a non-empty name.
Indenting Code
In the examples, I've been adding spaces in front of statements that are part of some larger statement. These spaces are not required - the computer will accept the program just fine without them. In fact, even the line breaks in programs are optional. You could write a program as a single long line if you felt like it.The role of this indentation inside blocks is to make the structure of the code stand out. In code where new blocks are opened inside other blocks, it can become hard to see where one block ends and another begins. With proper indentation, the visual shape of a program corresponds to the shape of the blocks inside it. I like to use two spaces for every open block, but tastes differ - some people use four spaces, and some people use tab characters. The important thing is that each new block adds the same amount of space.
if (false != true) { console.log("That makes sense."); if (1 < 2) { console.log("No surprise there."); } }
Most code editor programs (including the one in this book) will help by automatically indenting new lines the proper amount.
For loops
Many loops follow the pattern shown in thewhile
examples. First a "counter" binding is created to track the progress of the loop. Then comes a while
loop, usually with a test expression that checks whether the counter has reached
its end value. At the end of the loop body, the counter is updated to track progress.
Because this pattern is so common, JavaScript and similar languages provide a slightly shorter and more comprehensive
form, the for
loop.
for (let number = 0; number <= 12; number = number + 2) { console.log(number); } // → 0 // → 2 // … etcetera
This program is exactly equivalent to the earlier even-number-printing example. The only change is that all the statements that are related to the "state" of the loop are grouped together after for
.
The parentheses after a for
keyword must contain two semicolons. The part before the first semicolon initializes the loop, usually by defining a binding. The second part is the expression that checks whether the loop must continue. The final part updates the state of the loop after every iteration. In most cases, this is shorter and clearer
than a while
construct.
This is the code that computes 210 using for
instead of while
:
let result = 1; for (let counter = 0; counter < 10; counter = counter + 1) { result = result * 2; } console.log(result); // → 1024
Breaking Out of a Loop
Having the looping condition producefalse
is not the only way a loop can finish. There is a special statement called break
that has the effect of immediately jumping out of the enclosing loop.
This program illustrates the break
statement. It finds the first number that is both greater than or equal
to 20 and divisible by 7.
for (let current = 20; ; current = current + 1) { if (current % 7 == 0) { console.log(current); break; } } // → 21
Using the remainder (%
) operator is an easy way to test whether a number is divisible by another number.
If it is, the remainder of their division is zero.
The for
construct in the example does not have a part that checks for the end of the loop. This means
that the loop will never stop unless the break
statement inside is executed.
If you were to remove that break
statement or you accidentally write an end condition that always produces
true
, your program would get stuck in an infinite loop. A program stuck in an infinite loop will never finish running, which is usually a bad thing.
If you create an infinite loop in one of the examples on these pages, you'll usually be asked whether you want to stop the script after a few seconds. If that fails, you will have to close the tab that you're working in, or on some browsers close your whole browser, to recover.
The continue
keyword is similar to break
, in that it influences the progress of a loop. When
continue
is encountered in a loop body, control jumps out of the body and continues with the loop's next iteration.
Updating bindings succinctly
Especially when looping, a program often needs to "update" a binding to hold a value based on that binding's previous value.
counter = counter + 1;
JavaScript provides a shortcut for this.
counter += 1;
Similar shortcuts work for many other operators, such as result *= 2
to double result
or
counter -= 1
to count downward.
This allows us to shorten our counting example a little more.
for (let number = 0; number <= 12; number += 2) { console.log(number); }
For counter += 1
and counter -= 1
, there are even shorter equivalents: counter++
and counter--
.