Control Flow
As we saw in the section on variables, the initialization of variables is called an assigment statement. The assignment statement is just one instance of a statement. In JavaScript, there are three types of statements: (1) simple statements, (2) compound statements, and (3) control statements. The assignment statement is one kind of simple statement. Sequences of statements, like those found in a function or the entirety of a program, are compound statements. Control statements consist of two subtypes categories: (1) conditional statements and (2) iterative statements. In this section, we focus on the former.
Conditional statements are the backbone of simple decisions in a program. A conditional statement is a statement that specifies a test. If the test is satisfied, other statements are executed. Otherwise, JavaScript proceeds to the next statement. In JavaScript, we have several conditional statements: the if-statement, if-else-estatement, else-statement, ternary operators, and switch statements. Later, we will consider iterative statements, which specify repetition of statements.
The if-statement
The if-statement tells JavaScript to run a given block of code if a given condition is true. If the condition is false, then the code block is never run. The if-statement takes the following format:
if (<condition>) {
<statements>
}
For example:
if (2 > 1) {
console.log("True");
}
The code above says, "If 2 is greater than 1, display in the console 'True.'"
if (1 !== 1) {
console.log("True");
}
This code says, "If 1 is not equal to 1 in value or type, then display in the console 'True.'" Because the condition is false, the code block never runs.
Another example: Check if a number is odd or even, we can use the following code:
let num = 39;
if (num % 2 != 0) {
console.log("Odd number");
}
This returns "Odd number" in the console
The else-if-statement
The else-if-statement tells JavaScript: If the first condition (to the if-statement) is true, run the if-statement's block of code. If the first condition (to the if-statement) is false, check this second condition (to the else-statement), and if it is true, run the else-statement's block of code. The else-if-statement takes the following form:
if ("condition-is-true") {
"run-this-block-of-code";
}
else if ("this-condition-is-true") {
"run-this-block-of-code";
}
Note that JavaScript will only look at the else-if-statement's condition if the first condition, from the if-statement, is false. This means that an else-if-statement must always accompany an if-statement.
An if-statement can be accompanied by multiple else-if-statements:
if ("condition1-is-true") {
"run-this-code-block";
}
// Condition 1 is false? Check condition 2.
else if ("condition2-is-true") {
"run-this-code-block";
}
// Condition 2 is false? Check condition 3.
else if ("condition3-is-true") {
"run-this-code-block";
}
The else-statement
The else-statement acts like an "otherwise." I.e., the else-statement effectively tells JavaScript that this is the code that should be run if all the conditions before it fail. In a way, it sort of sets the "default" instruction to a set of conditionals. The else-statement takes the following form:
if ("condition1-is-true") {
"run-this-block-of-code";
}
else {
"run-this-block-of-code";
}
Limitation of else-statements Because JavaScript only executes an else-statement after checking an if-statement, the else-statement must always be preceded by an if-statement or an else-if-statement. Thus, an else-statement can never stand on its own. Furthermore, because an else-statement effectively provides the default, or fallback, instruction to JavaScript in a set of conditionals, there can only be one else-statement in a given block of conditionals.
Branching: Nesting Conditional Statements
Conditional statements can be nested within conditional statements. For example, below is some code that attempts to verify a password.
let password;
if (password.length >= 6) {
if (password.indexOf(" ") === -1) {
console.log("Valid password!");
} else {
console.log("Password cannot contain a space.");
}
} else {
console.log("Password must be longer.");
}
Let's run through the code.
The first condition: Check if the password has at least 6 characters. If it meets this condition, run the block code of inside the braces. First condition: Check if the password contains spaces. We can do this by using indexOf. If it does not contain spaces, it will return -1. If it does contain spaces, it will return a number other than -1.
Inside the block, we have another condition. If the password contains a space (i.e., the first condition is not strictly equal to 1, run this code below.)
If the password is not at least 6 characters (i.e., the first condition is not satisfied), run this code.
Inherent Boolean Values
In JavaScript, all values have an inherently true or false value. Values that are inherently false:
false
0
""
(an empty string)null
undefined
NaN
Every value other than the above has an inherently true value. For example:
let x = 3;
if (x) {
console.log("true");
}
The console displays "true" because 3 is inherently true, the if-statement's condition is satisfied, and so JavaScript executes its code block.
let y = NaN;
if (y) {
console.log("true");
} else {
console.log("false");
}
The console displays "false" because NaN is inherently false because the condition is not satisfied (the if-statement's condition is false), the else-statement's code block is run.
JavaScript Logical Operators
The logical operators provide a way to arrange, or connect, conditional statements. With the ability to connect and arrange conditional statements, we can write programs with more complex conditional statements. This in turn allows us to write more concise and efficient code.
AND Operator
The &&
operator allows us to streamline nested if-statements:
// Here is code that attempts to verify a password.
let password = "masterProgrammer";
// First condition: Check if these two sub-expressions are true:
// (1) The password has at least 6 characters.
// (2) The password contains no spaces.
if (password.length >= 6 && password.indexOf(" ") === -1) {
console.log("Valid password");
} else {
console.log("Invalid password");
}
&&
allows us to connect conditional statements into a single conditional
expression, called a conditional AND expression. We can connect as many
conditional expressions (i.e., conditions) as we want with &&
. For the
conditional AND expression to be true, all of its sub-expressions (the
connected conditions) must all be true. Thus, if a single sub-expression is
false, the entire conditional AND expression is false. Example:
// Condition 1: Check if these two sub-expressions are true:
// (1) Is 1 <= 4?
// (2) Is 'a' strictly equal to 'a'?
if (1 <= 4 && "a" === "a") {
console.log("true");
}
// Default code: If Condition 1 is false, run this code block.
else {
console.log("false");
}
Running the code above, the console returns true. Why is that? Because
1 <= 4
is true, and 'a' is strictly equal to 'a'.
Note, if just one of the sub-expressions is false, then the entire conditional AND statement is false. For example:
// Condition 1: Check if these two sub-expressions are true:
// (1) Is 9 <= 10?
// (2) Is 'a' strictly equal to 'a'?
if (9 > 10 && "a" === "a") {
console.log("true");
}
// If Condition 1 is false, run this code block.
else {
console.log("false");
}
Here, the console returns "false". Why? Because even though sub-expression (2) is true, sub-expression (1) is false, so the entire condition (1), a conditional AND statement, is false. Because condition (1) is false, the else-statement's code block is run.
OR Operator
The ||
operator allows us to connect sub-expressions into a single
conditional expression, called a conditional OR expression. If just one
of the sub-expressions is true, then the entire OR expression is true.
The code below returns true, since one of the sub-expressions, 10 === 10, is true.
1 !== 1 || 10 === 10;
This returns true, because one of the sub-expressions, 10/2 === 5
, is
true.
10 / 2 === 5 || null;
This returns false, because both 0 and undefined are inherently false (i.e., none of the sub-expressions are true.)
0 || undefined;
Below is a code example using the ||
operator. Suppose we provide a
service that gives users under 6 or over 60 a discount. We want to let the
user know, after inputting their age, that they get a discount.
First condition: Check if at least one of these sub-expressions is true: (1) The person's age is less than or equal to 6. (2) The person's age is greater than or equal to 60.
If none of the above sub-expressions is true, run this code block. The code:
let age = 64;
if (age <= 6 || age >= 60) {
console.log("You get a discount!");
} else {
console.log("Proceeding to checkout");
}
As an aside, note that JavaScript evaluates the &&
and ||
operators
using an evaluation model called short-circuit mode. Under this
evaluation model, JavaScript evaluates the right operand only if it needs
to. For example, given the expression a && b
, if JavaScript evaluates a
to be false
, it will immediately return false
for the entire
expression; it will not evaluate b
. It will, however, evaluate b
if a
evaluates to true
. Similarly, given the expression x || y
, if x
evaluates to true
, JavaScript will immediately evaluate the entire
expression as true
. It will not evaluate y
. But again, if x
evaluates
to false
, JavaScript will evaluate y
.
NOT Operator
The NOT operator reverses the values for a conditional expression. Thus, if
a conditional expression is true, if the NOT operator !
is attached to
the expression, then the conditional expression returns the oppositeā€”false.
Illustration:
!null; // Returns true; null is an inherently false value, and so its opposite is true.
!0; // Returns true
!""; // Returns true
!45; // Returns false
We can attach the NOT operator to AND conditionals or OR conditionals. Suppose we are running a bubble tea stand. We only have two flavors: taro or tea.
Condition 1: If the flavor inputted by the user is NOT "taro" or "tea", then run this code block.
let flavor = "blueberry";
if (!(flavor === "taro" || flavor === "tea")) {
console.log("Sorry, we only have taro and tea at the moment.");
} else {
console.log(`One ${flavor} bubble tea, coming right up!`);
}
Logical Operator Precedence
JavaScript follows a strict rule, called operator precedence, when evaluating complex conditional statements. For example, what does JavaScript return for the following:
let x = 7;
x == 7 || (x === 3 && x > 10);
Operator precedence applies:
- Conditional expressions contained inside parantheses are always evaluated first.
- The NOT operator is evaluated before the AND operator.
- The AND operator is evaluated before the OR operator.
Thus, in the above code, JavaScript evaluates the above code as:
let x = 7;
(x == 7 || x === 3) && x > 10;
If we want to change this behavior, we need to use parentheses:
let x = 7;
x == 7 || (x === 3 && x > 10);
Switch Statements
The switch statement allows us to perform different actions based on different conditions. The switch statement takes the following form:
switch(<expression>) {
case <possible value 1>:
<statement 1>;
case <possible value 2>:
<statement 2>;
}
Note that if a single case value matches the variable, JavaScript will execute all of the statements thereafter except the statements in the first match. This is called fall-through behavior, and is found in languages like C and C++. In some situations, this may be desirable, and in others, it may not be.1.
To ensure JavaScript stops at the first match, each case must include a
break
statement. This essentially tells JavaScript to stop and return the
first match the moment it's encountered.
For example, suppose we have a program where the user enters a number from 1 to 7, and gets back out a day of the wee. We could do the following:
let day = 8;
if (day === 1) {
console.log("Monday");
} else if (day === 2) {
console.log("Tuesday");
} else if (day === 3) {
console.log("Wednesday");
} else if (day === 4) {
console.log("Thursday");
} else if (day === 5) {
console.log("Friday");
} else if (day === 6) {
console.log("Saturday");
} else if (day === 7) {
console.log("Sunday");
} else {
console.log("Enter a number from 1 to 7.");
}
The above code accomplishes the task, but it can be written much more compactly with a switch statement:
let day = 1;
switch (day) {
case 1:
console.log("Monday");
break;
case 2:
console.log("Tuesday");
break;
case 3:
console.log("Wednesday");
break;
case 4:
console.log("Thursday");
break;
case 5:
console.log("Friday");
break;
case 6:
console.log("Saturday");
break;
case 7:
console.log("Sunday");
break;
default:
console.log("Enter a number from 1 to 7.");
}
Note how each of the cases contains a "break" statement. This is because without the break statement, the moment JavaScript arrives at a matching case, it will run the code block for everything thereafter. This default behavior is prevented by using the break statement.
A further note: Switch-case statements use the triple-equal operator inherently. This means that a case will match if, and only if, the switched value is both equal in value and type.
The Ternary Operator
If we have a single if-statement accompanied with an else-statement, we can write the statements as a single line of code with the ternary operator. The template for the ternary operator:
<expression1> ? <truth-case-value> : <false-case-value>;
For example, here is a program that displays in the console "Lucky Number 7" if the user picks 7:
let num = 7;
if (num === 7) {
console.log("Lucky!");
} else {
console.log("No luck.");
}
We can refactor the program with a switch statement:
let num = 7;
num === 7 ? console.log("Lucky!") : console.log("No luck.");
Converting to Booleans
Recall that every JavaScript literal has an inherently true or false value.
For example, the literal 1
is inherently true, and the literal ""
(an
empty string) is inherently false. We can convert these values to actual
Boolean values with explicit type conversion:
let x = 1;
let y = Boolean(x);
console.log(y);
true
Alternatively, we can use implicit type conversion:
let x = 1;
let y = !!x;
true
Notice the !!
syntax. This handy trick negates the inherent Boolean value
(false
), and negates it again (true
), which effectively returns an
actual Boolean value. This may not seem like much of an ability, but it can
lead to more concise code:
let names = ["John", "Jane", "", "", "Lori"];
console.log(names);
let formattedNames = names.filter(Boolean);
console.log(formattedNames);
['John', 'Jane', '', '', 'Lori']
['John', 'Jane', 'Lori']
Another example: Returning false
for a variable called isValidInput
if
the user enters an empty string:
const userInput = "";
const isValidInput = !!userInput;
Sum Types
In functional programming languages like ML, one of the most useful
features is the support for sum type data. This is essentially a piece of
data that can only be one of a discrete set of values. For example, say we
have the status of whether someone is dead. From a purely logical
standpoint, the person could be "dead"
, "alive"
. In the real world,
however, we have a third option: "unknown"
. We can accomplish this
representation with the OR operator in JavaScript:
let lifeStatus = deadOrAlive || "unknown";
In the code above, the value deadOrAlive
is some input from another part
of the program. If the value assigned to deadOrAlive
is "dead"
, then
the variable lifeStatus
is assigned the value "dead"
. If deadOrAlive
is assigned the value "alive"
, then lifeStatus
is assigned "alive"
.
If deadOrAlive
is undefined
(an inherently false
value), then
lifeStatus
is assigned the value "unknown"
(an inherently true
value).
Why does this work in JavaScript? Because the AND and OR operators, at a
low level, operate on literals. Because they are commonly used with
relational or comparison operators, they return Boolean literals most
often. However, when used with non-Boolean literals (e.g., a string
value), they will return the appropriate literal. In this case, the
operator OR returns "unknown"
(a string), rather than true
.
This idea extends to the AND operator. For example:
let discountApplies = (validCouponEntered && 4.98) || 0;
The code above assigns to the variable discountApplies
the value 4.98
if and only if the variable validCouponEntered
is initialized (or, in
this case, has a true
value). Otherwise, the assigned value defaults to
0
.
Footnotes
-
Notably, some languages (e.g., Swift) eschew fall-through behavior, concluding that users are not likely to use the feature. Accordingly, fall-through behavior is absent in Swift, unless the fallthrough keyword is used. ā†©