Control Structures

In this chapter, you learn about the control structures of the JavaScript language: branches, loops, and catching exceptions. The chapter also gives an overview of JavaScript statements and describes the process of automatic semicolon insertion.

Expressions and Statements

JavaScript, like Java and C++, differentiates between expressions and statements. An expression has a value. For example, 6 * 7 is an expression with value 42.

Try typing 6 * 7 into the JavaScript console or the node.js REPL. The value is displayed. That is what a read-eval-print loop does: it reads an expression, evaluates it, and prints the value. Now type:

console.log(6 * 7)

The response is:

42
undefined

The first line of output is the side effect of the console.log call. The second line is the return value of that call. As it happens, the console.log method returns undefined.

A statement never has a value. Instead, it is executed to achieve some effect.

For example,

let number = 6 * 7

is a statement whose effect is to declare and initialize the number variable.

Confusingly, the browser console and node.js REPL also display values when you enter statements—see . These values are not a standard part of JavaScript.

The simplest form of a statement is an expression statement. It consists of an expression, followed by a semicolon (which is added when you omit it—see ):

console.log(6 * 7);

This only makes sense for expressions that have a side effect. An expression statement

6 * 7;

is legal JavaScript, but it has no effect in a program.

Semicolon Insertion

In JavaScript, certain statements must be terminated with semicolons. The most common ones are variable declarations, expression statements, and non-linear control flow (break, continue, return, throw). However, JavaScript will helpfully insert semicolons for you.

The basic rule is simple. As a statement is parsed, every token is included until an explicit semicolon or an “offending token”—something that could not be a part of the statement. If the offending token is preceded by a line terminator, or is a } or the end of input, then a semicolon is added.

Here is an example:

let a = x
  + someComplicatedFunctionCall()
let b = y;

No semicolon is added after the first line. The + token at the start of the second line is not “offending”.

But the second let is offending. It could not have been a part of the first variable declaration. Because the offending token comes after a line terminator, a semicolon is inserted:

let a = x
  + someComplicatedFunctionCall();
let b = y;

That sounds simple enough, but there is a catch. When a statement starts with a token that could have been a part of the preceding statement, then no semicolon is inserted. Consider this example:

let x = a
(console.log(6 * 7))

No semicolon is inserted after a.

Syntactically,

a(console.log(6 * 7))

is valid JavaScript: calling a function a with the value returned by the call to console.log. In other words, the ( token on the second line was not an offending token.

Of course, this example is completely artificial. You don't need those parentheses. In practice, it is quite uncommon to run into similar grief.

Apart from the “offending token” rule, there are couple of other, more obscure, rules. A semicolon is inserted if a ++, --, or => is immediately preceded by a line terminator, or a break, continue, return, throw, or yield, is immediately followed by a line terminator.

Surely you will never break a line immediately before a postfix ++. The most plausible problem is a return statement (see Chapter 3) that is immediately followed by a line terminator. If you write

return
   someComplicatedExpression;

then a semicolon is automatically added:

return ;
   someComplicatedExpression;

The function returns without yielding any value. The second line is an expression statement that is never executed.

Again, the remedy is trivial. Put at least one token of the return value expression in the same line. If you don't return anything, add a semicolon.

Semicolons are only inserted before a line terminator or a }. If you have multiple statements on the same line, you need to provide semicolons:

if (i < j) { i++; j-- }

Now you know why I can't find any enthusiasm to argue for or against automatic semicolon insertion. Sure, the rules are weird, but they work tolerably well in practice. If you like semicolons, by all means, put them them in. If you don't, omit them when you can. Either way, you need to pay attention to a couple of corner cases.

Branches

The conditional statement in Java has the form

if (condition) statement

The condition must be surrounded by parentheses.

You will often want to execute multiple statements when a condition is fulfilled. In this case, use a block statement that takes the form

{
   statement1
   statement2
   . . .
}

An optional else clause is executed when the condition is not fulfilled:

For example:

if (yourSales >= target) {
  performance = 'Satisfactory'
  bonus = 100
} else {
  performance = 'Unsatisfactory'
  bonus = 0   
}

This example shows the “one true brace style” in which the opening brace is placed at the end of the line preceding the first statement of the block. This style is commonly used with JavaScript.

If the else clause is another if statement, the following format is conventionally used:

if (yourSales >= 2 * target) {
  performance = 'Excellent'
  bonus = 1000
} else if (yourSales >= target) {
  performance = 'Satisfactory'
  bonus = 100
} else {
  performance = 'Unsatisfactory'
  bonus = 0   
}

Braces are not necessary around single statements:

if (yourSales >= target) 
  bonus = 100
else
  bonus = 0

Some programmers insist on adding braces anyway.

Relational Operators

JavaScript has the usual assortment of comparison operators:

<  less than
<= less than or equal
>  greater than
>= greater than or equal

Any comparison involving NaN yields false:

NaN < 0 // false
NaN >= 0 // false
NaN <= NaN // false

The same operators also compare strings, using lexicographic comparison:

'Hello' < 'Goodbye' // false
'Hello' < 'Hi' // true

If one operand is a number and the other is a string, the string is converted to a number, yielding the numeric value if the string happens to contain a number, 0 if the string is empty, or NaN otherwise:

'42' < 50 // true' 42' is converted to the number 42
'' < 50 // true'' is converted to the number 0
'Hello' < 50 // false'Hello' is converted to NaN

Operands that are not numbers or strings are converted to numbers (if possible) or strings. If at least one value is a number, then both are compared as numbers, otherwise as strings. These comparisons don't usually yield meaningful outcomes:

[1, 2, 3] < {} // true[] is converted to '1,2,3', {} to '[object Object]'.

When comparing values with <, <=, >, >=, be sure that both operands are numbers or both operands are strings. Convert them explicitly if necessary. Do not rely on the implicit conversions.

There are four operators that test for equality.

=== strictly equal to
!== not strictly equal to
== loosely equal to
!= not loosely equal to

The strict equality operators are straightforward. If in the expression x === y, the two operands have different types, they are never equal. If they are both numbers, Boolean values, or strings, their values must be equal. The undefined and null values are only equal to themselves.

As in Java and Python, equality of objects (including arrays) means that the two operands refer to the same object. References to different objects are never equal, even if both objects have the same contents.

let harry = { name: 'Harry Smith', age: 42 }
let harry2 = harry
harry === harry2 // true—two references to the same object
let harry3 = { name: 'Harry Smith', age: 42 }
harry === harry3 // false—different objects

Loose equality x == y is more complex:

For example:

'' == 0 // true'' is converted to 0.
'0' == 0 // true'0' is converted to 0.
'0' == false // true—both are converted to 0.
null == false // falsenull is only equal to itself and undefined.

Have another look at the strings '' and '0'. They are both “equal to” 0. Are they “equal to” each other?

'' == '0' // false—no conversion since both operands are strings.

Many JavaScript programmers find the loose equality rules baffling and favor strict equality.

Always use the strict operators === and !== for equality tests. Do not use the loose == and != operators. The loose comparison rules are almost never useful and can easily lead to subtle errors.

The loose comparison x == null actually tests whether x is null or undefined, and x != null tests whether x is neither. This can be a convenient shortcut. Some programmers who have resolved never to use loose equality make an exception for this case.

Because it is possible to define a local variable with name undefined, some programmers recommend the following test for undefinedness:

typeof x === 'undefined'

If you have sufficient control over your code base to ensure that undefined is not redefined in the current scope, you can use the simple test:

x === undefined

You cannot use

x === NaN

to check whether x equals NaN. No two NaN values are considered to be equal to another. Instead, use the Number.isNaN method:

Number.isNaN(x)

But do not use the global isNaN function—it is broken for arguments that are not numbers:

isNaN('Hello') // true

Boolishness

In JavaScript, conditions (such as the one in the if statement) need not be Boolean values. The “falsish” values 0, NaN, null, undefined, and the empty string make the condition fail. All other values are “truish” and make the condition succeed.

Boolishness also applies for loop conditions, the operands of the Boolean operators &&, ||, and !, and the first operand of the ? : operator. All these constructs are covered later in this chapter.

The Boolean conversion rule sounds reasonable at first. Suppose you have a variable performance, and you only want to use it if it isn't null. So you write:

if (performance) . . . // Danger

At first, this looks great. As a freebie, your test also fails if performance is undefined.

But what if performance is the empty string? Or the number zero? Do you really want to treat these values the same way as absent values? Sometimes you do, and sometimes you don't. Shouldn't your code clearly indicate what your intent is? Just write what you mean:

if (performance !== null && performance !== '') . . .

Avoid boolishness. Produce true or false values in all conditions.

Boolean Operators

JavaScript has three operators to combine Boolean values:

&& and
|| or
!  not

The expression x && y is true if both x and y are true, and x || y is true if at least one of x and y are. The expression !x is true if x is false.

The && and || operators are evaluated lazily. If the left operand decides the result (falsish for &&, truish for ||), the right operand is not evaluated. This is often useful—for example:

if (i < a.length && a[i] > 0) // [i] > 0 is not evaluated if ia.length

The && and || operands have another curious twist if the operands are not Boolean values. They yield one of the operands as the expression value. If the left operand decides the result, it becomes the value of the expression, and the right operand is not evaluated. Otherwise, the expression value is the value of the right operand.

For example:

0 && 'Harry' // 0
0 || 'Harry' // 'Harry'

Some programmers take advantage of this behavior and write code such as the following:

let result = arg && arg.someMethod()

The intent is to check that arg isn't null or undefined before calling the method. If it is, then result is also null or undefined. This idiom works if one knows that arg cannot be zero, an empty string, or false.

Another use is to produce a default value when a method returns null or undefined:

let result = arg.someMethod() || 0

Again, you would need to carefully consider whether the method can return zero, an empty string, or false, and whether the default value is appropriate in these cases.

I won't use these idioms in this book. You have to decide for yourself whether to yield to the temptation of brevity over clarity.

JavaScript also has bitwise operators & | ^ ~ that first truncate their operands to 32-bit (!) integers and then combine their bits, exactly like their counterparts in Java or C++. There are shift operators << >> >>> that shift the bits, with the left operand truncated to a 32-bit integer and the right operand truncated to a 5-bit integer. If you need to fiddle with individual bits, go ahead and use these operators. Otherwise, stay away from them.

Some programmers use the expression x | 0 to remove the fractional part of a number x. This produces incorrect results if x ≥ 232. It is better to use Math.floor(x) instead.

while Loops

The while loop executes a statement (which may be a block statement) while a condition is fulfilled. The general form is

while (condition) statement

The following loop determines how long it will take to save a specific amount of money for your well-earned retirement, assuming you deposit the same amount of money per year and the money earns a specified interest rate.

let years = 0
while (balance < goal) {
  balance += paymentAmount
  let interest = balance * interestRate / 100
  balance += interest
  years++
}
console.log(years + ' years.')

for Loops

The for loop is a general construct to support iteration over elements. The following three sections discuss the variants that JavaScript offers.

The Classic for Loop

The classic form of the for loop is intended for a counter or similar variable that is updated after every iteration. The following loop logs the numbers from 1 to 10:

for (let i = 1; i <= 10; i++)
  console.log(i)

The first slot of the for statement holds the counter initialization. The second slot gives the condition that will be tested before each new pass through the loop, and the third slot specifies how to update the counter after each loop iteration.

The nature of the initialization, test, and update depends on the kind of traversal that you want. For example, this loop visits the elements of an array in reverse order:

for (let i = a.length - 1; i >= 0; i--)
  console.log(a[i])

JavaScript, like Java and C++, allows arbitrary variable declarations or expressions in the first slot, and arbitrary expressions in the other slots of a for loop. However, it is an unwritten rule of good taste that you should initialize, test, and update the same variable. Do not surprise your fellow programmers by disregarding this rule.

It is possible to cram multiple update expressions into the third slot of a for loop by using the comma operator:

for (let i = 0, j = a.length - 1; i < j; i++, j--) {
  let temp = a[i]
  a[i] = a[j]
  a[j] = temp
}

In the expression i++, j--, the comma operator joins the two expression i++ and j-- to a new expression. The value of a comma expression is the value of the second operand. In this situation, the value is unused—we only care about the side effects of incrementing and decrementing.

The comma operator is generally unloved because it can be confusing. For example, Math.max((4, 2)) is the maximum of the single value (4, 2) or 2. For that reason, Java has no general comma operator and only permits it in the third slot of a for loop.

The comma in the declaration let i = 0, j = a.length - 1 is not a comma operator but a syntactical part of the let statement. It is possible to declare multiple variables in any let statement. However. most JavaScript programmers use a separate let for each variable.

The for of loop

The for of loop iterates over the elements of an iterable object, most commonly an array or string. (In Chapter 4, you will see how to make other objects iterable.)

Here is an example:

let arr = [1, 2, , 4]
arr[99] = 100
for (const element of arr)
  console.log(element)

The loop visits all elements of the array in the correct order, by increasing index. The elements at index 2 and 4 through 98 are reported as undefined.

Note that the variable element is best declared as const. It is created in each loop iteration and initialized with the current element value. You should not change it in the loop body.

The for of loop is a pleasant improvement over the traditional loop if you need to process all elements in a array. However, there are still plenty of opportunities to use the classic for loop. For example, you might not want to traverse the entire array, or you may need the index value inside the loop.

When the for of loop iterates over a string, it visits each Unicode code point. That is the behavior that you want. For example:

let greeting = 'Hello 🌐'
for (c of greeting) console.log(c)
  // Prints H e l l o, a space, and 🌐

You need not worry about the fact that 🌐 uses two code units, stored in greeting[6] and greeting[7].

The for in loop

You cannot use the for of loop to iterate over the property values of an arbitrary object, and you probably wouldn't want to—the property values are usually meaningless without the keys. Instead, you can use the for in loop to visit all keys:

let obj = { name: 'Harry Smith', age: 42 }
for (const key in obj) {
  const value = obj[key]
  ...
}

The for in loop traverses the keys of the given object in some order. As you will see in Chapter 4, certain “nonenumerable” properties are skipped in the iteration, whereas “prototype” properties are included.

The for of loop in JavaScript is the same loop as the “generalized” for loop in Java, also called the “for each” loop. The for in loop in JavaScript has no Java equivalent.

You can use a for in loop to iterate over the index keys of an array.

let arr = [1, 2, , 4]
arr[99] = 100
for (const key in arr)
  console.log(key + ': ' + arr[key])

This loop sets key to "0", "1", "3", and "99", in some order. As for all JavaScript objects, the keys are strings.

Of course, if you add other properties to your array, they are also enumerated.

As you will see in Chapter 4, it is possible for others to add enumerable properties to Array.prototype or Object.prototype. Those will show up in a for in loop. Therefore, modern JavaScript etiquette strongly discourages this practice. Nevertheless, some programmers warn against the for in loop because they worry about legacy libraries or colleagues who paste random code from the internet.

When the for in loop iterates over a string, it visits the indexes of each Unicode code unit. That is probably not what you want. For example:

let greeting = 'Hello 🌐'
for (i of greeting) console.log(greeting[i])
  // Prints H e l l o, a space, and two broken symbols

The indexes 6 and 7 for the two code units of the Unicode character 🌐 are visited separately.

Catching Exceptions

Some methods return an error value when they are invoked with invalid arguments. For example, parseFloat('') returns a NaN value.

However, it is not always a good idea to return an error value. There may be no obvious way of distinguishing valid and invalid values. The parseFloat method is a good example. The call parseFloat('NaN') returns NaN, just like parseFloat('Infinity') returns the Infinity value. When parseFloat returns NaN, you cannot tell whether it parsed a valid 'NaN' string or an invalid argument.

JavaScript allows every method an alternative exit path if it is unable to complete its task in the normal way. In this situation, the method does not return a value. Instead, it throws an exception. Moreover, execution does not resume at the code that called the method. Instead, a catch clause is executed. If an exception is not caught anywhere, the program terminates.

To catch an exception, use a try statement. The simplest form of this statement is as follows:

try {
  code
  more code
  more code
} catch {
  handler
}

If any code inside the try block throws an exception, then the program skips the remainder of the code in the try block and executes the handler code inside the catch clause.

For example, suppose you receive a JSON string and parse it. The call to JSON.parse throws an exception if the argument is not valid JSON. You hamdle that situation in the catch clause:

try {
  let input = ...
  let data = JSON.parse(input)
  // If execution continues here, input is valid
  // Process data 
  ...
} catch {
  // Deal with the fact that the input is invalid
  ...
}

In the handler, you can log that information, or take some evasive action to deal with the fact that you were handed a bad JSON string.