buy the book to support the author.
Chapter 13. Statements
This chapter covers JavaScript’s statements: variable declarations, loops, conditionals, and others.
Declaring and Assigning Variables
var
is used to declare a variable, which creates the variable and enables you to work with it. The equals operator (=
) is used to assign a value to it:
var
foo
;
foo
=
'abc'
;
var
also lets you combine the preceding two statements into a single one:
var
foo
=
'abc'
;
Finally, you can also combine multiple var
statements into one:
var
x
,
y
=
123
,
z
;
Read more about how variables work in Chapter 16.
The Bodies of Loops and Conditionals
Compound statements such as loops and conditionals have one or more “bodies” embedded—for example, the while
loop:
while
(
«
condition
»
)
«
statement
»
For the body «statement»
, you have a choice. You can either use a single statement:
while
(
x
>=
0
)
x
--
;
or you can use a block (which counts as a single statement):
while
(
x
>
0
)
{
x
--
;
}
You need to use a block if you want the body to comprise multiple statements. Unless the complete compound statement can be written in a single line, I recommend using a block.
Loops
This section explores JavaScript’s loop statements.
Mechanisms to Be Used with Loops
The following mechanisms can be used with all loops:
break ⟦«label»⟧
- Exit from a loop.
continue ⟦«label»⟧
- Stop the current loop iteration, and immediately continue with the next one.
- Labels
- A label is an identifier followed by a colon. In front of a loop, a label allows you to break or continue that loop even from a loop nested inside of it. In front of a block, you can break out of that block. In both cases, the name of the label becomes an argument of
break
orcontinue
. Here’s an example of breaking out of a block:
function
findEvenNumber
(
arr
)
{
loop
:
{
// label
for
(
var
i
=
0
;
i
<
arr
.
length
;
i
++
)
{
var
elem
=
arr
[
i
];
if
((
elem
%
2
)
===
0
)
{
console
.
log
(
'Found: '
+
elem
);
break
loop
;
}
}
console
.
log
(
'No even number found.'
);
}
console
.
log
(
'DONE'
);
}
while
while
(
«
condition
»
)
«
statement
»
executes statement
as long as condition
holds. If condition
is always true
, you get an infinite loop:
while
(
true
)
{
...
}
In the following example, we remove all elements of an array and log them to the console:
var
arr
=
[
'a'
,
'b'
,
'c'
];
while
(
arr
.
length
>
0
)
{
console
.
log
(
arr
.
shift
());
}
Here is the output:
- a
- b
- c
do-while
A do-while
loop:
do
«
statement
»
while
(
«
condition
»
);
executes statement
at least once and then as long as condition
holds. For example:
var
line
;
do
{
line
=
prompt
(
'Enter a number:'
);
}
while
(
!
/^[0-9]+$/
.
test
(
line
));
for
In a for
loop:
for
(
⟦«
init
»⟧
;
⟦«
condition
»⟧
;
⟦«
post_iteration
»⟧
)
«
statement
»
init
is executed once before the loop, which continues as long as condition
is true
. You can use var
in init
to declare variables, but the scope of those variables is always the complete surrounding function. post_iteration
is executed after each iteration of the loop. Taking all of this into consideration, the preceding loop is equivalent to the following while
loop:
«
init
»
;
while
(
«
condition
»
)
{
«
statement
»
«
post_iteration
»
;
}
The following example is the traditional way of iterating over arrays (other possibilities are described in Best Practices: Iterating over Arrays):
var
arr
=
[
'a'
,
'b'
,
'c'
];
for
(
var
i
=
0
;
i
<
arr
.
length
;
i
++
)
{
console
.
log
(
arr
[
i
]);
}
A for
loop becomes infinite if you omit all parts of the head:
for
(;;)
{
...
}
for-in
A for-in
loop:
for
(
«
variable
»
in
«
object
»
)
«
statement
»
iterates over all property keys of object
, including inherited ones. However, properties that are marked as not enumerable are ignored (see Property Attributes and Property Descriptors). The following rules apply to for-in
loops:
- You can use
var
to declare variables, but the scope of those variables is always the complete surrounding function. - Properties can be deleted during iteration.
Best practice: don’t use for-in for arrays
Don’t use for-in
to iterate over arrays. First, it iterates over indices, not over values:
- > var arr = [ 'a', 'b', 'c' ];
- > for (var key in arr) { console.log(key); }
- 0
- 1
- 2
Second, it also iterates over all (nonindex) property keys. The following example illustrates what happens when you add a property foo
to an array:
- > var arr = [ 'a', 'b', 'c' ];
- > arr.foo = true;
- > for (var key in arr) { console.log(key); }
- 0
- 1
- 2
- foo
Thus, you are better off with a normal for
loop or the array method forEach()
(see Best Practices: Iterating over Arrays).
Best practice: be careful with for-in for objects
The for-in
loop iterates over all (enumerable) properties, including inherited ones. That may not be what you want. Let’s use the following constructor to illustrate the problem:
function
Person
(
name
)
{
this
.
name
=
name
;
}
Person
.
prototype
.
describe
=
function
()
{
return
'Name: '
+
this
.
name
;
};
Instances of Person
inherit the property describe
from Person.prototype
, which is seen by for-in
:
var
person
=
new
Person
(
'Jane'
);
for
(
var
key
in
person
)
{
console
.
log
(
key
);
}
Here is the output:
- name
- describe
Normally, the best way to use for-in
is to skip inherited properties via hasOwnProperty()
:
for
(
var
key
in
person
)
{
if
(
person
.
hasOwnProperty
(
key
))
{
console
.
log
(
key
);
}
}
And here is the output:
- name
There is one last caveat: person
may have a property hasOwnProperty
, which would prevent the check from working. To be safe, you have to refer to the generic method (see Generic Methods: Borrowing Methods from Prototypes) Object.prototype.hasOwnProperty
directly:
for
(
var
key
in
person
)
{
if
(
Object
.
prototype
.
hasOwnProperty
.
call
(
person
,
key
))
{
console
.
log
(
key
);
}
}
There are other, more comfortable, means for iterating over property keys, which are described in Best Practices: Iterating over Own Properties.
for each-in
This loop exists only on Firefox. Don’t use it.
Conditionals
This section covers JavaScript’s conditional statements.
if-then-else
In an if-then-else
statement:
if
(
«
condition
»
)
«
then_branch
»
⟦
else
«
else_branch
»⟧
then_branch
and else_branch
can be either single statements or blocks of statements (see The Bodies of Loops and Conditionals).
Chaining if statements
You can chain several if
statements:
if
(
s1
>
s2
)
{
return
1
;
}
else
if
(
s1
<
s2
)
{
return
-
1
;
}
else
{
return
0
;
}
Note that in the preceding example, all the else
branches are single statements (if
statements). Programming languages that only allow blocks for else
branches need some kind of else-if
branch for chaining.
Pitfall: dangling else
The else
branch of the following example is called dangling, because it is not clear to which of the two if
statements it belongs:
if
(
«
cond1
»
)
if
(
«
cond2
»
)
«
stmt1
»
else
«
stmt2
»
Here’s a simple rule: use braces. The preceding snippet is equivalent to the following code (where it is obvious who the else
belongs to):
if
(
«
cond1
»
)
{
if
(
«
cond2
»
)
{
«
stmt1
»
}
else
{
«
stmt2
»
}
}
switch
A switch
statement:
switch
(
«
expression
»
)
{
case
«
label1_1
»
:
case
«
label1_2
»
:
...
«
statements1
»
⟦
break
;
⟧
case
«
label2_1
»
:
case
«
label2_2
»
:
...
«
statements2
»
⟦
break
;
⟧
...
⟦
default
:
«
statements_default
»
⟦
break
;
⟧⟧
}
evaluates expression
and then jumps to the case
clause whose label matches the result. If no label matches, switch
jumps to the default
clause if it exists or does nothing otherwise.
The “operand” after case
can be any expression; it is compared via ===
with the parameter of switch
.
If you don’t finish a clause with a terminating statement, execution continues into the next clause. The most frequently used terminating statement is break
. But return
and throw
also work, even though they normally leave more than just the switch
statement.
The following example illustrates that you don’t need to break
if you use throw
or return
:
function
divide
(
dividend
,
divisor
)
{
switch
(
divisor
)
{
case
0
:
throw
'Division by zero'
;
default
:
return
dividend
/
divisor
;
}
}
In this example, there is no default
clause. Therefore, nothing happens if fruit
matches none of the case
labels:
function
useFruit
(
fruit
)
{
switch
(
fruit
)
{
case
'apple'
:
makeCider
();
break
;
case
'grape'
:
makeWine
();
break
;
// neither apple nor grape: do nothing
}
}
Here, there are multiple case
labels in a row:
function
categorizeColor
(
color
)
{
var
result
;
switch
(
color
)
{
case
'red'
:
case
'yellow'
:
case
'blue'
:
result
=
'Primary color: '
+
color
;
break
;
case
'orange'
:
case
'green'
:
case
'violet'
:
result
=
'Secondary color: '
+
color
;
break
;
case
'black'
:
case
'white'
:
result
=
'Not a color'
;
break
;
default
:
throw
'Illegal argument: '
+
color
;
}
console
.
log
(
result
);
}
This example demonstrates that the value after case
can be an arbitrary expression:
function
compare
(
x
,
y
)
{
switch
(
true
)
{
case
x
<
y
:
return
-
1
;
case
x
===
y
:
return
0
;
default
:
return
1
;
}
}
The preceding switch
statement looks for a match for its parameter true
by going through the case
clauses. If one of the case
expressions evaluates to true
, the corresponding case
body is executed.Therefore, the preceding code is equivalent to the following if
statement:
function
compare
(
x
,
y
)
{
if
(
x
<
y
)
{
return
-
1
;
}
else
if
(
x
===
y
)
{
return
0
;
}
else
{
return
1
;
}
}
You normally should prefer the latter solution; it is more self-explanatory.
The with Statement
This section explains how the with
statement works in JavaScript and why its use is discouraged.
Syntax and Semantics
The syntax of the with
statement is as follows:
with
(
«
object
»
)
«
statement
»
It turns the properties of object
into local variables for statement
. For example:
var
obj
=
{
first
:
'John'
};
with
(
obj
)
{
console
.
log
(
'Hello '
+
first
);
// Hello John
}
Its intended use is to avoid redundancy when accessing an object several times. The following is an example of code with redundancies:
foo
.
bar
.
baz
.
bla
=
123
;
foo
.
bar
.
baz
.
yadda
=
'abc'
;
with
makes this shorter:
with
(
foo
.
bar
.
baz
)
{
bla
=
123
;
yadda
=
'abc'
;
}
The with Statement Is Deprecated
- > function foo() { 'use strict'; with ({}); }
- SyntaxError: strict mode code may not contain 'with' statements
Techniques for avoiding the with statement
Avoid code like this:
// Don't do this:
with
(
foo
.
bar
.
baz
)
{
console
.
log
(
'Hello '
+
first
+
' '
+
last
);
}
Instead, use a temporary variable with a short name:
var
b
=
foo
.
bar
.
baz
;
console
.
log
(
'Hello '
+
b
.
first
+
' '
+
b
.
last
);
If you don’t want to expose the temporary variable b
to the currentscope, you can use an IIFE (see Introducing a New Scope via an IIFE):
(
function
()
{
var
b
=
foo
.
bar
.
baz
;
console
.
log
(
'Hello '
+
b
.
first
+
' '
+
b
.
last
);
}());
You also have the option of making the object that you want to access a parameter of the IIFE:
(
function
(
b
)
{
console
.
log
(
'Hello '
+
b
.
first
+
' '
+
b
.
last
);
}(
foo
.
bar
.
baz
));
The Rationale for the Deprecation
To understand why with
is deprecated, look at the following example and notice how the function’s argument completely changes how it works:
function
logMessage
(
msg
,
opts
)
{
with
(
opts
)
{
console
.
log
(
'msg: '
+
msg
);
// (1)
}
}
If opts
has a property msg
, then the statement in line (1) doesn’t access the parameter msg
anymore. It accesses the property:
- > logMessage('hello', {}) // parameter msg
- msg: hello
- > logMessage('hello', { msg: 'world' }) // property opts.msg
- msg: world
There are three problems that the with
statement causes:
- Performance suffers
- Variable lookup becomes slower, because an object is temporarily inserted into the scope chain.
- Code becomes less predictable
- You cannot determine what an identifier refers to by looking at its syntactic surroundings (its lexical context). According to Brendan Eich, that was the actual reason why
with
was deprecated, not performance considerations:
with
violates lexical scope, making program analysis (e.g. for security) hard to infeasible.
- Minifiers (described in Chapter 32) can’t shorten variable names
- Inside a
with
statement, you can’t statically determine whether a name refers to a variable or a property. Only variables can be renamed by minifiers.
Here is an example of with
making code brittle:
function
foo
(
someArray
)
{
var
values
=
...;
// (1)
with
(
someArray
)
{
values
.
someMethod
(...);
// (2)
...
}
}
foo
(
myData
);
// (3)
You can prevent the function call in line (3) from working, even if you don’t have access to the array myData
.
How? By adding a property values
to Array.prototype
. For example:
Array
.
prototype
.
values
=
function
()
{
...
};
Now the code in line (2) calls someArray.values.someMethod()
instead of values.someMethod()
. The reason is that, inside the with
statement, values
now refers to someArray.values
and not the local variable from line (1) anymore.
This is not just a thought experiment: the array method values()
was added to Firefox and broke the TYPO3 content management system. Brandon Benvie figured out what went wrong.
The debugger Statement
The syntax for the debugger
statement is as follows:
debugger
;
If a debugger is active, this statement functions as a breakpoint; if not, it has no observable effect.