034.3 Lesson 1
Certificate: |
Web Development Essentials |
---|---|
Version: |
1.0 |
Topic: |
034 JavaScript Programming |
Objective: |
034.3 JavaScript Control Structures and Functions |
Lesson: |
1 of 2 |
Introduction
Like any other programming language, JavaScript code is a collection of statements that tells an instruction interpreter what to do in sequential order. However, this does not mean that every statement should be executed only once or that they should execute at all. Most statements should execute only when specific conditions are met. Even when a script is triggered asynchronously by independent events, it often has to check a number of control variables to find out the right portion of code to run.
If Statements
The simplest control structure is given by the if
instruction, which will execute the statement immediately after it if the specified condition is true. JavaScript considers conditions to be true if the evaluated value is non-zero. Anything inside the parenthesis after the if
word (spaces are ignored) will be interpreted as a condition. In the following example, the literal number 1
is the condition:
if ( 1 ) console.log("1 is always true");
The number 1
is explicitly written in this example condition, so it is treated as a constant value (it remains the same throughout the execution of the script) and it will always yield true when used as a conditional expression. The word true
(without the double quotes) could also be used instead of 1
, as it is also treated as a literal true value by the language. The console.log
instruction prints its arguments in the browser’s console window.
Tip
|
The browser console shows errors, warnings, and informational messages sent with the |
Although syntactically correct, using constant expressions in conditions is not very useful. In an actual application, you’ll probably want to test the trueness of a variable:
let my_number = 3;
if ( my_number ) console.log("The value of my_number is", my_number, "and it yields true");
The value assigned to the variable my_number
(3
) is non-zero, so it yields true. But this example is not common usage, because you rarely need to test whether a number equals zero. It is much more common to compare one value to another and test whether the result is true:
let my_number = 3;
if ( my_number == 3 ) console.log("The value of my_number is", my_number, "indeed");
The double equal comparison operator is used because the single equal operator is already defined as the assignment operator. The value on each side of the operator is called an operand. The ordering of the operands does not matter and any expression that returns a value can be an operand. Here a list of other available comparison operators:
value1 == value2
-
True if
value1
is equal tovalue2
. value1 != value2
-
True if
value1
is not equal tovalue2
. value1 < value2
-
True if
value1
is less thanvalue2
. value1 > value2
-
True if
value1
is greater thanvalue2
. value1 <= value2
-
True if
value1
is less than or equal tovalue2
. value1 >= value2
-
True if
value1
is grater than or equal tovalue2
.
Usually, it does not matter whether the operand to the left of the operator is a string and the operand to the right is a number, as long as JavaScript is able to convert the expression to a meaningful comparison. So the string containing the character 1
will be treated as the number 1 when compared to a number variable. To make sure the expression yields true only if both operands are of the exact same type and value, the strict identity operator ===
should be used instead of ==
. Likewise, the strict non-identity operator !==
evaluates as true if the first operand is not of the exact same type and value as the second operator.
Optionally, the if
control structure can execute an alternate statement when the expression evaluates as false:
let my_number = 4;
if ( my_number == 3 ) console.log("The value of my_number is 3");
else console.log("The value of my_number is not 3");
The else
instruction should immediately follow the if
instruction. So far, we are executing only one statement when the condition is met. To execute more than one statement, you must enclose them in curly braces:
let my_number = 4;
if ( my_number == 3 )
{
console.log("The value of my_number is 3");
console.log("and this is the second statement in the block");
}
else
{
console.log("The value of my_number is not 3");
console.log("and this is the second statement in the block");
}
A group of one or more statements delimited by a pair of curly braces is known as a block statement. It is common to use block statements even when there is only one instruction to execute, in order to make the coding style consistent throughout the script. Moreover, JavaScript does not require the curly braces or any statements to be on separate lines, but to do so improves readability and makes code maintenance easier.
Control structures can be nested inside each other, but it is important not to mix up the opening and closing braces of each block statement:
let my_number = 4;
if ( my_number > 0 )
{
console.log("The value of my_number is positive");
if ( my_number % 2 == 0 )
{
console.log("and it is an even number");
}
else
{
console.log("and it is an odd number");
}
} // end of if ( my_number > 0 )
else
{
console.log("The value of my_number is less than or equal to 0");
console.log("and I decided to ignore it");
}
The expressions evaluated by the if
instruction can be more elaborate than simple comparisons. This is the case in the previous example, where the arithmetical expression my_number % 2
was employed inside the parentheses in the nested if
. The %
operator returns the remainder after dividing the number on its left by the number on its right. Arithmetical operators like %
take precedence over comparison operators like ==
, so the comparison will use the result of the arithmetical expression as its left operand.
In many situations, nested conditional structures can be combined into a single structure using logical operators. If we were interested only in positive even numbers, for example, a single if
structure could be used:
let my_number = 4;
if ( my_number > 0 && my_number % 2 == 0 )
{
console.log("The value of my_number is positive");
console.log("and it is an even number");
}
else
{
console.log("The value of my_number either 0, negative");
console.log("or it is a negative number");
}
The double ampersand operator &&
in the evaluated expression is the logical AND operator. It evaluates as true only if the expression to its left and the expression to its right evaluate as true. If you want to match numbers that are either positive or even, the ||
operator should be used instead, which stands for the OR logical operator:
let my_number = -4;
if ( my_number > 0 || my_number % 2 == 0 )
{
console.log("The value of my_number is positive");
console.log("or it is a even negative number");
}
In this example, only negative odd numbers will fail to match the criteria imposed by the composite expression. If you have the opposite intent, that is, to match only the negative odd numbers, add the logical NOT operator !
to the beginning of the expression:
let my_number = -5;
if ( ! ( my_number > 0 || my_number % 2 == 0 ) )
{
console.log("The value of my_number is an odd negative number");
}
Adding the parenthesis in the composite expression forces the expression enclosed by them to be evaluated first. Without these parentheses, the NOT operator would apply only to my_number > 0
and then the OR expression would be evaluated. The &&
and ||
operators are known as binary logical operators, because they require two operands. !
is known as a unary logical operator, because it requires only one operand.
Switch Structures
Although the if
structure is quite versatile and sufficient to control the flow of the program, the switch
control structure may be more appropriate when results other than true or false need to be evaluated. For example, if we want to take a distinct action for each item chosen from a list, it will be necessary to write an if
structure for each assessment:
// Available languages: en (English), es (Spanish), pt (Portuguese)
let language = "pt";
// Variable to register whether the language was found in the list
let found = 0;
if ( language == "en" )
{
found = 1;
console.log("English");
}
if ( found == 0 && language == "es" )
{
found = 1;
console.log("Spanish");
}
if ( found == 0 && language == "pt" )
{
found = 1;
console.log("Portuguese");
}
if ( found == 0 )
{
console.log(language, " is unknown to me");
}
In this example, an auxiliary variable found
is used by all if
structures to find out whether a match has occurred. In a case such as this one, the switch
structure will perform the same task, but in a more succinct manner:
switch ( language )
{
case "en":
console.log("English");
break;
case "es":
console.log("Spanish");
break;
case "pt":
console.log("Portuguese");
break;
default:
console.log(language, " not found");
}
Each nested case
is called a clause. When a clause matches the evaluated expression, it executes the statements following the colon until the break
statement. The last clause does not need a break
statement and is often used to set the default action when no other matches occur. As seen in the example, the auxiliary variable is not needed in the switch
structure.
Warning
|
|
If more than one clause triggers the same action, you can combine two or more case
conditions:
switch ( language )
{
case "en":
case "en_US":
case "en_GB":
console.log("English");
break;
case "es":
console.log("Spanish");
break;
case "pt":
case "pt_BR":
console.log("Portuguese");
break;
default:
console.log(language, " not found");
}
Loops
In previous examples, the if
and switch
structures were well suited for tasks that need to run only once after passing one or more conditional tests. However, there are situations when a task must execute repeatedly — in a so-called loop — as long as its conditional expression keeps testing as true. If you need to know whether a number is prime, for example, you will need to check whether dividing this number by any integer greater than 1 and lower than itself has a remainder equal to 0. If so, the number has an integer factor and it is not prime. (This is not a rigorous or efficient method to find prime numbers, but it works as a simple example.) Loop controls structures are more suitable for such cases, in particular the while
statement:
// A naive prime number tester
// The number we want to evaluate
let candidate = 231;
// Auxiliary variable
let is_prime = true;
// The first factor to try
let factor = 2;
// Execute the block statement if factor is
// less than candidate and keep doing it
// while factor is less than candidate
while ( factor < candidate )
{
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
// The next factor to try. Simply
// increment the current factor by one
factor++;
}
// Display the result in the console window.
// If candidate has no integer factor, then
// the auxiliary variable is_prime still true
if ( is_prime )
{
console.log(candidate, "is prime");
}
else
{
console.log(candidate, "is not prime");
}
The block statement following the while
instruction will execute repeatedly as long as the condition factor < candidate
is true. It will execute at least once, as long as we initialize the factor
variable with a value lower than candidate
. The if
structure nested in the while
structure will evaluate whether the remainder of candidate
divided by factor
is zero. If so, the candidate number is not prime and the loop can finish. The break
statement will finish the loop and the execution will jump to the first instruction after the while
block.
Note that the outcome of the condition used by the while
statement must change at every loop, otherwise the block statement will loop “forever”. In the example, we increment the factor
variable‒the next divisor we want to try‒and it guarantees the loop will end at some point.
This simple implementation of a prime number tester works as expected. However, we know that a number that is not divisible by two will not be divisible by any other even number. Therefore, we could just skip the even numbers by adding another if
instruction:
while ( factor < candidate )
{
// Skip even factors bigger than two
if ( factor > 2 && factor % 2 == 0 )
{
factor++;
continue;
}
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
// The next number that will divide the candidate
factor++;
}
The continue
statement is similar to the break
statement, but instead of finishing this iteration of the loop, it will ignore the rest of the loop’s block and start a new iteration. Note that the factor
variable was modified before the continue
statement, otherwise the loop would have the same outcome again in the next iteration. This example is too simple and skipping part of the loop will not really improve its performance, but skipping redundant instructions is very important when writing efficient applications.
Loops are so commonly used that they exist in many different variants. The for
loop is specially suited to iterate through sequential values, because it lets us define the loop rules in a single line:
for ( let factor = 2; factor < candidate; factor++ )
{
// Skip even factors bigger than two
if ( factor > 2 && factor % 2 == 0 )
{
continue;
}
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
}
This example produces the exact same result as the previous while
example, but its parenthetic expression includes three parts, separated by semicolons: the initialization (let factor = 2
), the loop condition (factor < candidate
), and the final expression to be evaluated at the end of each loop iteration (factor++
). The continue
and break
statements also apply to for
loops. The final expression in the parentheses (factor++
) will be evaluated after the continue
statement, so it should not be inside the block statement, otherwise it will be incremented two times before the next iteration.
JavaScript has special types of for
loops to work with array-like objects. We could, for example, check an array of candidate variables instead of just one:
// A naive prime number tester
// The array of numbers we want to evaluate
let candidates = [111, 139, 293, 327];
// Evaluates every candidate in the array
for (candidate of candidates)
{
// Auxiliary variable
let is_prime = true;
for ( let factor = 2; factor < candidate; factor++ )
{
// Skip even factors bigger than two
if ( factor > 2 && factor % 2 == 0 )
{
continue;
}
if ( candidate % factor == 0 )
{
// The remainder is zero, so the candidate is not prime
is_prime = false;
break;
}
}
// Display the result in the console window
if ( is_prime )
{
console.log(candidate, "is prime");
}
else
{
console.log(candidate, "is not prime");
}
}
The for (candidate of candidates)
statement assigns one element of the candidates
array to the candidate
variable and uses it in the block statement, repeating the process for every element in the array. You don’t need to declare candidate
separately, because the for
loop defines it. Finally, the same code from the previous example was nested in this new block statement, but this time testing each candidate in the array.
Guided Exercises
-
What values of the
my_var
variable match the conditionmy_var > 0 && my_var < 9
? -
What values of the
my_var
variable match the conditionmy_var > 0 || my_var < 9
? -
How many times does the following
while
loop execute its block statement?let i = 0; while ( 1 ) { if ( i == 10 ) { continue; } i++; }
Explorational Exercises
-
What happens if the equal assignment operator
=
is used instead of the equal comparison operator==
? -
Write a code fragment using the
if
control structure where an ordinary equality comparison will return true, but a strict equality comparison will not. -
Rewrite the following
for
statement using the unary NOT logical operator in the loop condition. The outcome of the condition should be the same.for ( let factor = 2; factor < candidate; factor++ )
-
Based on the examples from this lesson, write a loop control structure that prints all the integer factors of a given number.
Summary
This lesson covers how to use control structures in JavaScript code. Conditional and loop structures are essential elements of any programming paradigm, and JavaScript web development is no exception. The lesson goes through the following concepts and procedures:
-
The
if
statement and comparison operators. -
How to use the
switch
structure withcase
,default
, andbreak
. -
The difference between ordinary and strict comparison.
-
Loop control structures:
while
andfor
.
Answers to Guided Exercises
-
What values of the
my_var
variable match the conditionmy_var > 0 && my_var < 9
?Only numbers that are both greater than 0 and less than 9. The
&&
(AND) logical operator requires both comparisons to match. -
What values of the
my_var
variable match the conditionmy_var > 0 || my_var < 9
?Using the
||
(OR) logical operator will cause any number to match, as any number will either be greater than 0 or less than 9. -
How many times does the following
while
loop execute its block statement?let i = 0; while ( 1 ) { if ( i == 10 ) { continue; } i++; }
The block statement will repeat indefinitely, as no stop condition was supplied.
Answers to Explorational Exercises
-
What happens if the equal assignment operator
=
is used instead of the equal comparison operator==
?The value on the right side of the operator is assigned to the variable on the left and the result is passed to the comparison, which may not be the desired behavior.
-
Write a code fragment using the
if
control structure where an ordinary equality comparison will return true, but a strict equality comparison will not.let a = "1"; let b = 1; if ( a == b ) { console.log("An ordinary comparison will match."); } if ( a === b ) { console.log("A strict comparison will not match."); }
-
Rewrite the following
for
statement using the unary NOT logical operator in the loop condition. The outcome of the condition should be the same.for ( let factor = 2; factor < candidate; factor++ )
Answer:
for ( let factor = 2; ! (factor >= candidate); factor++ )
-
Based on the examples from this lesson, write a loop control structure that prints all the integer factors of a given number.
for ( let factor = 2; factor <= my_number; factor++ ) { if ( my_number % factor == 0 ) { console.log(factor, " is an integer factor of ", my_number); } }