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.
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
.
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)
Never start a statement with (
or [
. Then you don’t have to worry about the statement being considered a continuation of the previous line.
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.
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.
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.
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 }
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
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
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.
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.
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) . . .
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' //false
—H
comes afterG
'Hello' < 'Hi' //true
—e
comes beforei
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.
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)
.
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
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 number42
'' < 5 //true
—''
is converted to the number0
'Hello' <= 5 //false
—'Hello'
is converted toNaN
5 <= 'Hello' //false
—'Hello'
is converted toNaN
Now suppose the other operand is an array:
[4] < 5 //true
—[4]
is converted to the number4
[] < 5 //true
—[]
is converted to the number0
[3, 4] < 5 //false
—[3, 4]
is converted toNaN
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 !==
).
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.
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
.
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.
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.
switch
StatementJavaScript 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.
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.
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.
while
and do
LoopsThis 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.
for
LoopsThe for
loop is a general construct for iterating over elements. The following three sections discuss the variants that JavaScript offers.
for
LoopThe 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])
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.
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
.
for of
LoopThe 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 '
for (const 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]
.
for in
LoopYou 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.
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.
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.
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).
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 (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 are visited separately.
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 afterbreak
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.
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.
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
What is wrong with the statement
if (x === 0) console.log('zero') else console.log('nonzero')
How do you fix the problem?
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?
What are the results of comparing undefined
, null
, 0
, and ''
values with the operators < <= ==
? Why?
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?
Use the three kinds of for
loop for finding the largest value in an array of numbers.
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?
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?
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.
Rewrite the do
loop in Section 2.9, “while
and do
Loops” (page 40), as a while
loop.
Rewrite all for
loops in Section 2.10, “for
Loops” (page 41), as while
loops.
Rewrite the labeled break
example in Section 2.11, “Breaking and Continuing” (page 44), to use two nested for
loops.
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.
Rewrite the continue
example in Section 2.11, “Breaking and Continuing” (page 44), without a continue
statement.
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
.
3.147.42.168