if
statementsThis blog post is part of the series “Learning web development” – which teaches people who have never programmed before how to create web apps with JavaScript.
To download the examples, go to the GitHub repository learning-web-dev-code
and follow the instructions there.
I’m interested in feedback! If there is something you don’t understand, please write a comment at the end of this page.
In this chapter, we learn about tools for only running a piece of code if a condition is met: truth values (booleans), comparisons and if
statements.
So far, we know two data types: numbers and strings. And both have many different values: 0
, -5
, 7.1
, etc.
Booleans are different – the only boolean values are true
and false
. They are returned by a function or method if it answers a yes-no question – e.g., the method Number.isInteger()
tells us if its argument is an integer or not:
> Number.isInteger(1)
true
> Number.isInteger(5.0)
true
> Number.isInteger(5.1)
false
Each string has a method .startsWith()
that tells us if it starts with the string provided as the argument:
> 'How are you?'.startsWith('How')
true
> 'How are you?'.startsWith('Why')
false
Each array has a method .includes()
that tells us if the array has an element that is equal to the argument:
> ['a', 'b', 'c'].includes('a')
true
> ['a', 'b', 'c'].includes('x')
false
Before we can learn more, we need to explore a little bit of theory – because that helps us understand what comes later.
JavaScript makes an important distinction:
undefined
, booleans, numbers, and strings.A primitive value is:
num
– it puts a new number into the variable:let num = 0;
num = num + 1;
If we assign a primitive value to a variable, it is stored in that variable.
An object is:
> const arr = ['a', 'b', 'c'];
> arr[1] = 'x';
> arr
[ 'a', 'x', 'c' ]
We didn’t put a new value into the variable arr
, we modified its current value.If we assign an object to a variable, it looks like that object is store in that variable. And it’s usually good to pretend that that’s what happens. However, what actually happens is different – e.g.:
const arr = ['a', 'b', 'c'];
arr
.Why does JavaScript store identities in variables? Objects can potentially be quite large. Assignment copies values and we want to avoid copying large amounts of memory. That’s the benefit of using identities for objects: The following assignment only copies the identity of an array, not the array itself:
const arr1 = ['a', 'b', 'c'];
const arr2 = arr;
Interestingly, arr1
and arr2
now refer to the same array. They share that array. Therefore, if we change the array that arr1
refers to, we also change the array that arr2
refers to – because it’s the same array and because we don’t change the variables, we only change what they refer to:
> arr1[1] = 'x';
> arr1
[ 'a', 'x', 'c' ]
> arr2
[ 'a', 'x', 'c' ]
This is what the variables look like immediately after the assignment:
The diagram shows two different kinds of memory (RAM):
Both variables refer to the same array on the heap.
With primitive values such as numbers, we change the variables (which is why we can’t use const
and have to use let
):
> let num1 = 123;
> let num2 = num1;
> num2
123
> num1 = 0;
> num2
123
This is what the variables look like immediately after the assignment:
This time, there is nothing on the heap. The variables are enough for storing numbers.
===
The strict equality operator ===
tells us if its operands are equal or not.
As an aside, there is also a lenient equality operator ==
but that one has many pitfalls and my recommendation is not to use it – especially if you are new to JavaScript.
If the operands of ===
are primitive values, they are compared by looking at their contents – which is also how humans would handle this task:
> 1 === 1
true
> 1 === 2
false
> 'hello' === 'hello'
true
> 'bye' === 'hello'
false
> true === true
true
> true === false
false
This way of comparing is called comparing by value.
If the operands of ===
are objects, their identities are compared – e.g.:
> const arr1 = [];
> const arr2 = arr1;
> arr1 === arr2
true
In this case, arr1
and arr2
refer to the same object, which is why ===
considers them to be equal.
In the next example, we compare two arrays that humans would consider to be equal (they have the some content). However, they are not equal to ===
because it compares identities and its operands have different identities:
> [] === []
false
This way of comparing is called comparing by identity.
===
Let’s use ===
to implement a function that detects if a string is empty:
const isEmpty = (str) => {
return str === '';
};
This is the function is action:
> isEmpty('')
true
> isEmpty('abc')
false
<
, <=
, >
, >=
JavaScript also has operators that correspond to mathematical operators:
<
corresponds to < (“less than”)<=
corresponds to ≤ (“less than or equal to”)>
corresponds to > (“greater than”)>=
corresponds to ≥ (“greater than or equal to”)Numbers are compared numerically – there shouldn’t be any surprises here:
> 3 < 3
false
> 3 <= 3
true
> 4 > 2
true
> 4 >= 2
true
Strings are compared lexicographically – similarly to how dictionaries order their words:
> 'apple' < 'banana'
true
This is sometimes useful, but has issues such as uppercase letters being greater than lowercase letters:
> 'apple' < 'Banana'
false
This kind of comparison doesn’t handle accents (é) and umlauts (ö) well, either. Therefore, we need to use other tools if we truly want to order things like in a dictionary. That isn’t a simple task because the rules are different for each human language.
&&
) and logical Or (||
) For numbers, we have operators such as *
and +
. For booleans, we have operators such as &&
and ||
. One one hand, these boolean operators follow very simple rules (which we’ll see soon). On the other hand, it’s fascinating how intuitive they are if their operands are expressions themselves.
&&
) Lets consider how human language handles “and”:
JavaScript’s logical And operator &&
works the same:
> const num = 3;
> (num > 0) && (num < 9)
true
> (num > 5) && (num < 9)
false
How is the last expression computed? JavaScript first computes the results of the operands and feeds those results to &&
:
> num > 5
false
> num < 9
true
> false && true
false
The following table describes the results of &&
for all possible inputs:
op1 |
op2 |
op1 && op2 |
---|---|---|
true |
true |
true |
false |
false |
false |
true |
false |
false |
false |
true |
false |
These results make intuitive sense: In English, “A and B” is only true if both A and B are true.
&&
) The human language “or” is more like an “either-or”. Compare:
I’d argue that humans consider #1 to be equivalent to #2. JavaScript’s logical Or operator is an “and/or”: op1 || op2
is true
if at least one of the operands is true
.
| op1
| op2
| op1 || op2
|
|---------|---------|--------------|
| true
| true
| true
|
| false
| false
| false
|
| true
| false
| true
|
| false
| true
| true
|
!
) Logical Not (!
) returns the opposite of a boolean value:
> !true
false
> !false
true
isBetween()
The following function detects if a value
is between (including) a lower boundary and (including) an upper boundary:
const isBetween = (lower, upper, value) => {
return (value >= lower) && (value <= upper);
};
This is the function in use:
> isBetween(0, 5, 1) // Is 1 between 0 and 5?
true
> isBetween(0, 5, 6) // Is 6 between 0 and 5?
false
We also could have used !
and ||
to implement this function:
const isBetween = (lower, upper, value) => {
return !((value < lower) || (value > upper));
};
NaN
Let’s quickly learn something that we’ll need in the next section: Numbers have the error value NaN
(which means “not a number”). NaN
indicates that something went wrong – e.g., if Number()
can’t convert a string to a number, it returns NaN
:
> Number('abc')
NaN
Another is example is Math.sqrt()
returning NaN
whenever a number doesn’t have a square root that is a real number:
> Math.sqrt(-1)
NaN
If we want to check if a value is NaN
, we unfortunately can’t use ===
:
> NaN === NaN
false
However, the method Number.isNaN()
lets us perform that check:
> Number.isNaN(NaN)
true
> Number.isNaN(4)
false
if
statements if
branches So far, boolean values were only moderately useful. However, with if
statements, they can control whether or not a piece of code is executed. This is the syntax of an if
statement:
if (bool) {
// Only executed if `bool` is `true`
}
This if
statement only manages a single piece of code whose name is:
true
branchif
branchHowever, we can use else
to add a second branch to the if
statement:
if (bool) {
// Only executed if `bool` is `true`
} else {
// Only executed if `bool` is `false`
}
The name of the second branch is:
false
branchelse
branchThe following function describes if we are happy:
const iAmHappy = (bool) => {
if (bool) {
return 'I am happy! 🙂';
} else {
return 'I am not happy! ☹️';
}
};
Let’s use that function:
> iAmHappy(true)
'I am happy! 🙂'
> iAmHappy(false)
'I am not happy! ☹️'
if
branches We can use else if
to add more branches:
if (bool1) {
// `bool1` is `true`
} else if (bool2) {
// `bool1` is `false` and `bool2` is `true`
} else {
// `bool1` is `false` and `bool2` is `false`
}
The following function uses three if
branches:
const describeNumber = (num) => {
if (Number.isNaN(num)) {
return 'Not a number';
} else if (Number.isInteger(num)) {
return 'An integer';
} else {
return 'A floating point number';
}
};
Let’s try it out:
> describeNumber(NaN)
'Not a number'
> describeNumber(4)
'An integer'
> describeNumber(4.1)
'A floating point number'
Note that return
always immediately leaves a function. Therefore, we could also have implemented describeNumber()
like this:
const describeNumber = (num) => {
if (Number.isNaN(num)) {
return 'Not a number';
}
if (Number.isInteger(num)) {
return 'An integer';
}
return 'A floating point number';
};
describe-input.html
This project provides a user interface for the function describeNumber()
from the previous section.
The input for describeNumber()
comes from an <input>
HTML element:
<input type="text">
The output of describeNumber()
is written to a <p>
HTML element:
<p id="description"></p>
The following JavaScript code controls the user interface:
const description = document.querySelector('#description');
document.querySelector('input')
.addEventListener(
'change',
(event) => {
const str = event.target.value;
const num = Number(str);
description.innerText = describeNumber(num);
}
);
Whenever a 'change'
happens, the event listener retrieves the input, feeds it to describeNumber()
and writes the result to the HTML element whose ID is 'description'
.
describe-input.html
: Change describeNumber()
so that it distinguishes between:
number-guessing-game.html
This game “thinks” of a number and the user has to guess which one it is. The game helps by telling the user if a guess is correct, too high or too low.
The game gets its input via a text field:
<input type="number" id="guessedNumberInput" disabled>
The input is submitted via a button:
<button id="guessButton" disabled>Guess:</button>
The output is displayed inside a <p>
HTML element:
<p id="feedback"></p>
Note that both <input>
and <button>
are initially disabled
: We don’t want users to submit answers until the game has started.
The following function starts the game (it is triggered by button that is not shown here):
let randomNumber = undefined;
const startGame = () => {
randomNumber = getRandomIntegerBetween(LOWER, UPPER + 1);
// Remove feedback from previous game (if there was one)
feedback.innerText = '';
guessButton.disabled = false;
guessedNumberInput.disabled = false;
};
The constants LOWER
and UPPER
are defined elsewhere and determine the range of the number to be guessed. Function getRandomIntegerBetween()
is shown later.
We are now ready to receive input from the user, which is why we enable guessButton
and guessedNumberInput
.
Whenever a new guess is submitted by a user, the following function is called:
const continueGame = (guessedNumber) => {
//----- Error checks -----
if (Number.isNaN(guessedNumber)) { // (A)
feedback.innerText = '❌ Your guess must be a number!';
return;
}
if (guessedNumber < LOWER || guessedNumber > UPPER) {
// Parentheses are not required but help with readability
feedback.innerText = (
'❌ Your guess must be ' +
'at least ' + LOWER + ' and ' +
'at most ' + UPPER + '.'
);
return;
}
//----- Guess is valid – process it -----
if (guessedNumber === randomNumber) {
feedback.innerText = '✅ Your guess is correct!';
guessButton.disabled = true;
guessedNumberInput.disabled = true;
} else if (guessedNumber < randomNumber) {
feedback.innerText = 'Your guess is smaller than my number.';
} else {
// guessedNumber > randomNumber
feedback.innerText = 'Your guess is larger than my number.';
}
};
The user might input a non-number such as the text “abc”. The input is converted to a number via Number()
– which is why the check in line A protects us from such a case:
> Number('abc')
NaN
After checking if the guessed number is within bounds, we give feedback to the user. If they have guessed correctly, then the game is finished. We disable guessButton
and guessedNumberInput
so that no further inputs are sent to continueGame()
.
LOWER
and UPPER
The HTML contains the following paragraph:
<p>
I’m thinking of a number between
<span id="lower">0</span> and
<span id="upper">99</span>.
</p>
The JavaScript code extracts these values as follows:
const LOWER = Number(document.querySelector('#lower').innerText);
const UPPER = Number(document.querySelector('#upper').innerText);
getRandomIntegerBetween()
You don’t have to understand how the following function works, but it may be interesting to try:
/** Returns a random integer i with min <= i < max */
const getRandomIntegerBetween = (min, max) => {
return min + Math.floor(Math.random() * (max - min));
};
It is easier to understand if we start with a simpler version of it that we have already encountered:
/** Returns a random integer i with 0 <= i < max */
const getRandomInteger = (max) => {
return Math.floor(Math.random() * max);
};
Steps performed by getRandomIntegerBetween()
:
getRandomInteger()
does: It computes a random number in the range [0, max - min)
(square bracket means “included”; parenthesis means “excluded”). This range has the same “length” as [min, max)
.min
to it:
0
then the final result will be min
.max - min - 1
then the final result will be max - 1
.number-guessing-game.html
: Remove the button and retrieve input via 'change'
events (which are triggered by users pressing Return).
number-guessing-game.html
: Change this game so that the user has to guess letters between “a” and “z”. Make sure that the input is only a single character and within range. You can use the property .length
for that purpose.