Chapter 2. Control Structures

Images

In this chapter, you will 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.

2.1 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. A method call such as Math.max(6, 7) is another example of an expression.

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. Such a statement is called a variable declaration.

Apart from variable declarations, other common statement types are branches and loops. You will see those later in this chapter.

The simplest form of a statement is an expression statement. It consists of an expression, followed by a semicolon. Here is an example:

console.log(6 * 7);

The expression console.log(6 * 7) has a side effect—displaying 42 on the console. It also has a value, which happens to be undefined, since the console.log method has chosen not to return anything more interesting. Even if the expression had a more interesting value, it would not matter—the value of an expression statement is discarded.

Therefore, an expression statement is only useful for an expression that has a side effect. The expression statement

6 * 7;

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

It is useful to understand the difference between expressions and statements—but in JavaScript, it is a bit tricky to see the difference between an expression and an expression statement. As you will see in the next section, a semicolon is automatically added if you write a line containing a single expression, turning it into a statement. For that reason, you cannot observe an expression in a browser’s JavaScript console or in Node.js.

For example, try typing 6 * 7. The value of the expression is displayed:

6 * 7
42

That is what a read-eval-print loop, or REPL, does: It reads an expression, evaluates it, and prints the value.

Except, because of automatic semicolon insertion, the JavaScript REPL actually sees the statement

6 * 7;

Statements don’t have values, but the JavaScript REPL displays values for them anyway.

Try typing in a variable declaration:

let number = 6 * 7;
undefined

As you just saw, for an expression statement, the REPL displays the value of the expression. For a variable declaration, the REPL displays undefined. Exercise 1 explores what is displayed for other statements.

When you run your own experiments with the REPL, it is important that you know how to interpret the output. For example, type in this expression statement and observe the response:

console.log(6 * 7);
42
undefined

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

2.2 Semicolon Insertion

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

The basic rule is simple. When processing a statement, the parser includes every token until it encounters a semicolon or an “offending token”—something that could not be part of the statement. If the offending token is preceded by a line terminator, or is a }, or is the end of input, then the parser adds a semicolon.

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 let token at the start of the third line 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

The “offending token” rule is simple, and it works well in almost all cases. However, it fails when a statement starts with a token that could have been a part of the preceding statement. 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: It calls 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 rather artificial. The parentheses around console.log(6 * 7) were not necessary. Here is another commonly cited example:

let a = x
[1, 2, 3].forEach(console.log)

Because a [ can appear after x, no semicolon is inserted. In the unlikely case that you want to loop over an array literal in this way, store the array in a variable:

let a = x
const numbers = [1, 2, 3]
numbers.forEach(console.log)

Images Tip

Never start a statement with ( or [. Then you don’t have to worry about the statement being considered a continuation of the previous line.

Images Note

In the absence of a semicolon, a line starting with a template or regular expression literal can be merged with the preceding line, for example:

let a = x
`Fred`.toUpperCase()

Here, x`Fred` is parsed as a tagged template literal. But you would never write such code in practice. When you work with a string or regular expression, you want to use the result, and the literal won’t be at the start of the statement.

The second semicolon rule can be more problematic. A semicolon is inserted after a nonlinear control flow statement (break, continue, return, throw, or yield) that is immediately followed by a line terminator. If you write

return
  x + someComplicatedExpression;

then a semicolon is automatically added:

return ;
  x + someComplicatedExpression;

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

The remedy is trivial. Don’t put a line break after return. Put at least one token of the return value expression in the same line:

return x +
  someComplicatedExpression;

You must pay attention to this rule even if you faithfully put semicolons everywhere.

Apart from the “offending token” and “nonlinear control flow” rules, there is another obscure rule. A semicolon is inserted if a ++ or -- is immediately preceded by a line terminator.

According to this rule,

x
++
y

means

x;
++y;

As long as you keep the ++ on the same line as its operand, you don’t have to worry about this rule.

The automatic insertion rules are part of the language. They work tolerably well in practice. If you like semicolons, by all means, put them in. If you don’t, omit them. Either way, you need to pay attention to a couple of corner cases.

Images Note

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-- }

Here, the semicolon is necessary to separate the i++ and j-- statements.

2.3 Branches

If you are familiar with C, C++, Java, or C#, you can safely skip this section.

The conditional statement in JavaScript has the form

if (condition) statement

The condition must be surrounded by parentheses.

Images Tip

In the condition, you should produce either true or false, even though JavaScript allows arbitrary values and converts them to Boolean values. As you will see in the next section, these conversions can be unintuitive and potentially dangerous. Follow the golden rule #3 from the preface:

  • Know your types and avoid automatic type conversion.

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 = 'Good'
  bonus = 100
} else {
  performance = 'Mediocre'
  bonus = 0
}

Images Note

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 = 'Good'
  bonus = 100
} else {
  performance = 'Mediocre'
  bonus = 0
}

Braces are not necessary around single statements:

if (yourSales > target)
  bonus = 100

Images Caution

If you don’t use braces, or if you use braces but not the “one true brace style” with an if/else statement, then you can write code that works in a program file but fails when pasting into a JavaScript console. Consider this example:

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

Some JavaScript consoles analyze the code one line at a time. Such a console will think that the if statement is complete before the else clause. To avoid this problem, use braces or place the entire if statement in a single line

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

It is sometimes convenient to have an expression analog to the if statement. Consider computing the larger of two values:

let max = undefined
if (x > y) max = x; else max = y

It would be nicer to initialize max with the larger of x and y. Since if is a statement, we cannot write:

let max = if (x > y) x else y // Error—if statement not expected

Instead, use the ? : operator, also called the “conditional” operator. The expression condition ? first : second evaluates to first if the condition is fulfilled, second otherwise. This solves our problem:

let max = x > y ? x : y

Images Note

The expression x > y ? x : y is a convenient example to illustrate the conditional operator, but you should use the standard library method Math.max if you need the largest of two or more values.

2.4 Boolishness

Images

This is a “mad hatter” section that describes a confusing feature of JavaScript in some detail. Feel free to skip the section if you follow the advice of the preceding section and only use Boolean values in conditions.

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. These are also often called “falsy” or “truthy.” None of these is an official term in the language specification.

Images Note

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 glance. Suppose you have a variable performance, and you only want to use it if it isn’t undefined. So you write:

if (performance) . . . // Danger

Sure, the test fails as expected if performance is undefined. As a freebie, it also fails if performance is null.

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 !== undefined) . . .

2.5 Comparison and Equality Testing

JavaScript has the usual assortment of comparison operators:

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

When used to compare numbers, these operators are unsurprising:

3 < 4 // true
3 >= 4 // false

Any comparison involving NaN yields false:

NaN < 4 // false
NaN >= 4 // false
NaN <= NaN // false

The same operators also compare strings, using lexicographic order.

'Hello' < 'Goodbye' // falseH comes after G
'Hello' < 'Hi' // truee comes before i

When comparing values with <, <=, >, >=, be sure that both operands are numbers or both operands are strings. Convert operands explicitly if necessary. Otherwise, JavaScript will convert operands for you, sometimes with undesirable results—see the following section.

Use these operators to test for equality:

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

The strict equality operators are straightforward. Operands of different types are never strictly equal. The undefined and null values are only strictly equal to themselves. Numbers, Boolean values, and strings are strictly equal if their values are equal.

'42' === 42 // false—different types
undefined === null // false
'42' === '4' + 2 // true—same string value '42'

There are also “loose equality” operators == and != that can compare values of different types. This is not generally useful—see the following section if you care about the details.

Images Caution

You cannot use

x === NaN

to check whether x equals NaN. No two NaN values are considered to be equal to one another. Instead, call Number.isNaN(x).

Images Note

Object.is(x, y) is almost the same as x === y, except that Object.is(+0, -0) is false and Object.is(NaN, NaN) is true.

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

2.6 Mixed Comparisons

Images

This is another “mad hatter” section that describes potentially confusing features of JavaScript in some detail. By all means, skip the section if you follow the advice of the golden rule #3—to avoid mixed-type comparisons, and in particular the “weak equality” operators (== and !=).

Still here? Let’s first look at mixed-type comparisons with the <, <=, >, >= operators.

If one operand is a number, the other operand is converted to a number. Suppose the other operand is a string. The conversion yields the numeric value if the string happens to contain a number, 0 if the string is empty, or NaN otherwise. Moreover, any comparison involving NaN is false—even NaN <= NaN.

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

Now suppose the other operand is an array:

[4] < 5 // true[4] is converted to the number 4
[] < 5 // true[] is converted to the number 0
[3, 4] < 5 // false[3, 4] is converted to NaN

If neither operand is a number, both are converted to strings. These comparisons rarely yield meaningful outcomes:

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

Next, let us look at loose equality x == y more closely. Here is how it works:

  • If the two operands have the same type, compare them strictly.

  • The values undefined and null are loosely equal to themselves and each other but not to any other values.

  • If one operand is a number and the other a string, convert the string to a number and compare strictly.

  • If one operand is a Boolean value, convert both to numbers and compare strictly.

  • If one operand is an object but the other is not, convert the object to a primitive type (see Chapter 8), then compare loosely.

For example:

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

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

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

As you can see, the loose comparison rules are not very useful and can easily lead to subtle errors. Avoid this quagmire by using strict equality operators (=== and !==).

Images Note

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

2.7 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) // a[i] > 0 is not evaluated if i ≥ a.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 try to 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 undefined or null before calling the method. If it is, then result is also undefined or null. This idiom breaks down if arg is zero, an empty string, or false.

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

let result = arg.someMethod() || defaultValue

Again, this breaks down if the method can return zero, an empty string, or false.

What is needed is a convenient way of using a value unless it is undefined or null. Two operators for this are, as of early 2020, in “proposal stage 3,” which means they are likely to be adopted in a future version of JavaScript.

The expression x ?? y yields x if x is not undefined or null, and y otherwise. In the expression

let result = arg.someMethod() ?? defaultValue

the default value is used only when the method returns undefined or null.

The expression x?.propertyName yields the given property if x is not undefined or null, and undefined otherwise. Consider

let recipient = person?.name

If person is neither undefined nor null, then the right-hand side is exactly the same as person.name. But if person is undefined or null, then recipient is set to undefined. If you had used the . operator instead of ?., then an exception would have occurred.

You can chain the ?. operators:

let recipientLength = person?.name?.length

If person or person.name is undefined or null, then recipientLength is set to undefined.

Images Note

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 of 32-bit integers, go ahead and use these operators. Otherwise, stay away from them.

Images Caution

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

2.8 The switch Statement

Images

JavaScript has a switch statement that is just like the switch statement in C, C++, Java, and C#—warts and all. Skip this section if you are familiar with switch.

The switch statement compares an expression with many possible values. Here is an example:

let description = ''
switch (someExpression) {
  case 0:
    description = 'zero'
    break
  case false:
  case true:
    description = 'boolean'
    break
  case '':
    description = 'empty string' // See the “Caution” note below
  default:
    description = 'something else'
}

Execution starts at the case label that strictly equals the value of the expression and continues until the next break or the end of the switch statement. If none of the case labels match, then execution starts at the default label if it is present.

Since strict equality is used for matching, case labels should not be objects.

Images Caution

If you forget to add a break at the end of an alternative, execution falls through to the next alternative! This happens in the preceding example when value is the empty string. The description is first set to 'empty string', then to 'something else'. This “fall through” behavior is plainly dangerous and a common cause for errors. For that reason, some developers avoid the switch statement.

Images Tip

In many cases, the difference in performance between a switch statement and the equivalent set of if statements is negligible. However, if you have a large number of cases, then the virtual machine can use a “jump table” for efficiently jumping to the appropriate case.

2.9 while and do Loops

This is another section that you can skip if you know C, C++, Java, or C#.

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.`)

The while loop will never execute if the condition is false at the outset. If you want to make sure a block is executed at least once, you need to move the test to the bottom, using the do/while loop. Its syntax looks like this:

do statement while (condition)

This loop executes the statement (which is typically a block) and then tests the condition. If the condition is fulfilled, the statement and the test are repeated. Here is an example. Suppose we just processed s[i] and are now looking at the next space in the string:

do {
  i++
} while (i < s.length && s[i] != ' ')

When the loop ends, either i is past the end of the string, or s[i] is a space.

The do loop is much less common than the while loop.

2.10 for Loops

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

2.10.1 The Classic for Loop

The classic form of the for loop works just like in C, C++, Java, or C#. It works with 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. 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])

Images Tip

You can place 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.

Images Note

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 expressions 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((9, 3)) is the maximum of the single value (9, 3)—that is, 3.

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. This statement declares two variables i and j.

2.10.2 The for of Loop

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

Here is an example:

let arr = [, 2, , 4]
arr[9] = 100
for (const element of arr)
  console.log(element) // Prints undefined, 2, undefined, 4, undefined (5 times), 100

The loop visits all elements of the array from index 0 to arr.length − 1, in increasing order. The elements at indexes 0, 2, and 4 through 8 are reported as undefined.

The variable element is created in each loop iteration and initialized with the current element value. It is declared as const since it is not changed in the loop body.

The for of loop is a pleasant improvement over the classic for 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 Images'
for (const c of greeting)
  console.log(c) // Prints H e l l o, a space, and Images

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

2.10.3 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, visit the keys with the for in loop:

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

This loop prints age: 42 and name: Harry Smith in some order.

The for in loop traverses the keys of the given object. As you will see in Chapters 4 and 8, “prototype” properties are included in the iteration, whereas certain “nonenumerable” properties are skipped. The order in which the keys are traversed depends on the implementation, so you should not rely on it.

Images Note

The for of loop in JavaScript is the same 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 property names of an array.

let numbers = [1, 2, , 4]
numbers[99] = 100
for (const i in numbers)
  console.log(`${i}: ${numbers[i]}`)

This loop sets i to '0', '1', '3', and '99'. Note that, as for all JavaScript objects, the property keys are strings. Even though common JavaScript implementations iterate over arrays in numerical order, it is best not to rely on that. If the iteration order matters to you, it is best to use a for of loop or a classic for loop.

Images Caution

Beware of expressions such as numbers[i + 1] in a for in loop. For example,

if (numbers[i] === numbers[i + 1]) // Error! i + 1 is '01', '11', and so on

The condition does not compare adjacent elements. Since i holds a string, the + operator concatenates strings. If i is '0', then i + 1 is '01'.

To fix this problem, convert the string i to a number:

if (numbers[i] === numbers[parseInt(i) + 1])

Or use a classic for loop.

Of course, if you add other properties to your array, they are also visited:

numbers.lucky = true
for (const i in numbers) // i is '0', '1', '3', '99', 'lucky'
  console.log(`${i}: ${numbers[i]}`)

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.

Images Note

In the next chapter, you will learn about another way of iterating over an array, using functional programming techniques. For example, you can log all array elements like this:

arr.forEach((element, key) => { console.log(`${key}: ${element}`) })

The provided function is called for all elements and index keys (as numbers 0 1 3 99, not strings).

Images Caution

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 Images'
for (const 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 Images are visited separately.

2.11 Breaking and Continuing

Images

Sometimes, you want to exit a loop as soon as you reach a goal. Suppose you look for the position of the first negative element in an array:

let i = 0
while (i < arr.length) {
  if (arr[i] < 0) . . .
  . . .
}

Upon seeing a negative element, you just want to exit the loop, so that i stays at the position of the element. That is what the break statement accomplishes.

let i = 0
while (i < arr.length) {
  if (arr[i] < 0) break
  i++
}
// Get here after break or when the loop terminates normally

The break statement is never necessary. You can always add a Boolean variable to control the loop termination—often called something like done or found:

let i = 0
let found = false
while (!found && i < arr.length) {
  if (arr[i] < 0) {
    found = true
  } else {
    i++
  }
}

Like Java, JavaScript offers a labeled break statement that lets you break out of multiple nested loops. Suppose you want to find the location of the first negative element in a two-dimensional array. When you have found it, you need to break out of two loops. Add a label (that is, an identifier followed by a colon) before the outer loop. A labeled break jumps after the labeled loop:

let i = 0
let j = 0
outer:
while (i < arr.length) {
  while (j < arr[i].length) {
    if (arr[i][j] < 0) break outer
    j++
  }
  i++
  j = 0
}
// Get here after break outer or when both loops terminate normally

The label in a labeled break statement must be on the same line as the break keyword.

Labeled breaks are not common.

Finally, there is a continue statement that, like the break statement, breaks the regular flow of control. The continue statement transfers control to the end of the innermost enclosing loop. Here is an example—averaging the positive elements of an array:

let count = 0
let sum = 0
for (let i = 0; i < arr.length; i++) {
  if (arr[i] <= 0) continue
  count++
  sum += arr[i]
}
let avg = count === 0 ? 0 : sum / count

When an element is not positive, the continue statement jumps immediately to the loop header, skipping the remainder of only the current iteration.

If a continue statement is used in a for loop, it jumps to the “update” part of the for loop, as in this example.

There is also a labeled form of the continue statement that jumps to the end of the loop with the matching label. Such statements are very uncommon.

Many programmers find the break and continue statements confusing. They are easily avoided, and in this book, I will not use them.

2.12 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.

In JavaScript, a method can take an alternative exit path if it is unable to complete its task in the normal way. Instead of returning a value, a method can throw an exception. In that case, 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. Handle that situation in the catch clause:

let input = . . . // Read input from somewhere
try {
  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.

In Chapter 3, you will see additional variations of the try statement that give you more control over the exception handling process. There, you will also see how to throw your own exceptions.

Exercises

  1. Browser consoles and the Node.js REPL display values when you enter statements. What values are displayed for the following kinds of statements?

    • An expression statement

    • A variable declaration

    • A block statement with at least one statement inside

    • An empty block statement

    • A while, do, or for loop whose body is executed at least once

    • A loop whose body is never executed

    • An if statement

    • A try statement that completes normally

    • A try statement whose catch clause is executed

  2. What is wrong with the statement

    if (x === 0) console.log('zero') else console.log('nonzero')

    How do you fix the problem?

  3. Consider a statement

    let x = a

    Which tokens could start the next line that prevent a semicolon to be inserted? Which ones can realistically occur in an actual program?

  4. What are the results of comparing undefined, null, 0, and '' values with the operators < <= ==? Why?

  5. Is a || b always the same as a ? a : b, no matter what type a and b are? Why or why not? Can you express a && b in a similar way?

  6. Use the three kinds of for loop for finding the largest value in an array of numbers.

  7. Consider this code snippet:

    let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
    for (i in arr) { if (i + 1 === 10) console.log(a[i]) }

    Why doesn’t it print anything?

  8. Implement a switch statement that converts digits 0 through 9 to their English names 'zero' through 'nine'. How can you do this easily without a switch? What about the reverse conversion?

  9. Suppose n is a number between 0 and 7 and you are supposed to set the array elements arr[k] through arr[k + n - 1] to zero. Use a switch with fallthrough.

  10. Rewrite the do loop in Section 2.9, “while and do Loops” (page 40), as a while loop.

  11. Rewrite all for loops in Section 2.10, “for Loops” (page 41), as while loops.

  12. Rewrite the labeled break example in Section 2.11, “Breaking and Continuing” (page 44), to use two nested for loops.

  13. Rewrite the labeled break example in Section 2.11, “Breaking and Continuing” (page 44), without a break statement. Introduce a Boolean variable to control the termination of the nested loops.

  14. Rewrite the continue example in Section 2.11, “Breaking and Continuing” (page 44), without a continue statement.

  15. Consider the problem of finding the first position in which an array b occurs as a subsequence of an array a. Write two nested loops:

    let result = undefined
    for (let i = 0; i < a.length - b.length; i++) {
      for (let j = 0; j < b.length; j++) {
        if (a[i + j] != b[j]) . . .
      }
      . . .
    }

    Complete with labeled break and continue statements. Then rewrite without break or continue.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.42.168