Chapter 7. JavaScript’s Syntax
JavaScript’s syntax is fairly straightforward. This chapter describes things to watch out for.
An Overview of the Syntax
This section gives you a quick impression of what JavaScript’s syntax looks like.
The following are five fundamental kinds of values:
Here are a few examples of basic syntax:
// Two slashes start single-linecomments
var
x
;
// declaring a variable
x
=
3
+
y
;
// assigning a value to the variable `x`
foo
(
x
,
y
);
// calling function `foo` with parameters `x` and `y`
obj
.
bar
(
3
);
// calling method `bar` of object `obj`
// A conditional statement
if
(
x
===
0
)
{
// Is `x` equal to zero?
x
=
123
;
}
// Defining function `baz` with parameters `a` and `b`
function
baz
(
a
,
b
)
{
return
a
+
b
;
}
Note the two different uses of the equals sign:
-
A single equals sign (
=
) is used to assign a value to a variable. -
A triple equals sign (
===
) is used to compare two values (see Equality Operators).
Comments
There are two kinds of comments:
Single-line comments via
//
extend to the rest of the line. Here’s an example:var
a
=
0
;
// init
Multiline comments via
/* */
can extend over arbitrary ranges of text. They cannot be nested. Here are two examples:/* temporarily disabled
processNext(queue);
*/
function
(
a
/* int */
,
b
/* str */
)
{
}
Expressions Versus Statements
This section looks at an important syntactic distinction in JavaScript: the difference between expressions and statements.
Expressions
An expression produces a value and can be written wherever a value is expected—for example, as an argument in a function call or at the right side of an assignment. Each of the following lines contains an expression:
myvar
3
+
x
myfunc
(
'a'
,
'b'
)
Statements
Roughly, a statement performs an action. Loops and if
statements are examples of statements. A program is basically a sequence of statements.[8]
Wherever JavaScript expects a statement, you can also write an expression. Such a statement is called an expression statement. The reverse does not hold: you cannot write a statement where JavaScript expects an expression. For example, an if
statement cannot become the argument of a function.
Conditional statement versus conditional expressions
The difference between statements and expressions becomes clearer if we look at members of the two syntactic categories that are similar: the if
statement and the conditional operator (an expression).
The following is an example of an if
statement:
var
salutation
;
if
(
male
)
{
salutation
=
'Mr.'
;
}
else
{
salutation
=
'Mrs.'
;
}
There is a similar kind of expression, the conditional operator. The preceding statements are equivalent to the following code:
var
salutation
=
(
male
?
'Mr.'
:
'Mrs.'
);
The code between the equals sign and the semicolon is an expression. The parentheses are not necessary, but I find the conditional operator easier to read if I put it in parens.
Using ambiguous expressions as statements
Two kinds of expressions look like statements—they are ambiguous with regard to their syntactic category:
Object literals (expressions) look like blocks (statements):
{
foo
:
bar
(
3
,
5
)
}
The preceding construct is either an object literal (details: Object Literals) or a block followed by the label
foo:
, followed by the function callbar(3, 5)
.Named function expressions look like function declarations (statements):
function
foo
()
{
}
The preceding construct is either a named function expression or a function declaration. The former produces a function, the latter creates a variable and assigns a function to it (details on both kinds of function definition: Defining Functions).
In order to prevent ambiguity during parsing, JavaScript does not let you use object literals and function expressions as statements. That is, expression statements must not start with:
- A curly brace
-
The keyword
function
If an expression starts with either of those tokens, it can only appear in an expression context. You can comply with that requirement by, for example, putting parentheses around the expression. Next, we’ll look at two examples where that is necessary.
Evaluating an object literal via eval()
eval
parses its argument in statement context. You have to put parentheses around an object literal if you want eval
to return an object:
> eval('{ foo: 123 }') 123 > eval('({ foo: 123 })') { foo: 123 }
Immediately invoking a function expression
The following code is an immediately invoked function expression (IIFE), a function whose body is executed right away (you’ll learn what IIFEs are used for in Introducing a New Scope via an IIFE):
> (function () { return 'abc' }()) 'abc'
If you omit the parentheses, you get a syntax error, because JavaScript sees a function declaration, which can’t be anonymous:
> function () { return 'abc' }() SyntaxError: function statement requires a name
If you add a name, you also get a syntax error, because function declarations can’t be immediately invoked:
> function foo() { return 'abc' }() SyntaxError: Unexpected token )
Whatever follows a function declaration must be a legal statement and ()
isn’t.
Control Flow Statements and Blocks
For control flow statements, the body is a single statement. Here are two examples:
if
(
obj
!==
null
)
obj
.
foo
();
while
(
x
>
0
)
x
--
;
However, any statement can always be replaced by a block, curly braces containing zero or more statements. Thus, you can also write:
if
(
obj
!==
null
)
{
obj
.
foo
();
}
while
(
x
>
0
)
{
x
--
;
}
I prefer the latter form of control flow statement. Standardizing on it means that there is no difference between single-statement bodies and multistatement bodies. As a consequence, your code looks more consistent, and alternating between one statement and more than one statement is easier.
Rules for Using Semicolons
In this section, we examine how semicolons are used in JavaScript. The basic rules are:
- Normally, statements are terminated by semicolons.
- The exception is statements ending with blocks.
Semicolons are optional in JavaScript. Missing semicolons are added via so-called automatic semicolon insertion (ASI; see Automatic Semicolon Insertion). However, that feature doesn’t always work as expected, which is why you should always include semicolons.
No Semicolon After a Statement Ending with a Block
The following statements are not terminated by semicolons if they end with a block:
-
Loops:
for
,while
(but notdo-while
) -
Branching:
if
,switch
,try
- Function declarations (but not function expressions)
Here’s an example of while
versus do-while
:
while
(
a
>
0
)
{
a
--
;
}
// no semicolon
do
{
a
--
;
}
while
(
a
>
0
);
And here’s an example of a function declaration versus a function expression. The latter is followed by a semicolon, because it appears inside a var
declaration (which is terminated by a semicolon):
function
foo
()
{
// ...
}
// no semicolon
var
foo
=
function
()
{
// ...
};
Note
If you do add a semicolon after a block, you do not get a syntax error, because it is considered an empty statement (see the next section).
Tip
That’s most of what you need to know about semicolons. If you always add semicolons, you can probably get by without reading the remaining parts of this section.
The Empty Statement
A semicolon on its own is an empty statement and does nothing. Empty statements can appear anywhere a statement is expected. They are useful in situations where a statement is demanded, but not needed. In such situations, blocks are usually also allowed. For example, the following two statements are equivalent:
while
(
processNextItem
()
>
0
);
while
(
processNextItem
()
>
0
)
{}
The function processNextItem
is assumed to return the number of
remaining items. The following program, consisting of three empty statements, is also syntactically correct:
;;;
Automatic Semicolon Insertion
The goal of automatic semicolon insertion (ASI) is to make semicolons optional at the end of a line. The image invoked by the term automatic semicolon insertion is that the JavaScript parser inserts semicolons for you (internally, things are usually handled differently).
Put another way, ASI helps the parser to determine when a statement ends. Normally, it ends with a semicolon. ASI dictates that a statement also ends if:
- A line terminator (e.g., a newline) is followed by an illegal token.
- A closing brace is encountered.
- The end of the file has been reached.
Example: ASI via illegal token
The following code contains a line terminator followed by an illegal token:
if
(
a
<
0
)
a
=
0
console
.
log
(
a
)
The token console
is illegal after 0
and triggers ASI:
if
(
a
<
0
)
a
=
0
;
console
.
log
(
a
);
Example: ASI via closing brace
In the following code, the statement inside the braces is not terminated by a semicolon:
function
add
(
a
,
b
)
{
return
a
+
b
}
ASI creates a syntactically correct version of the preceding code:
function
add
(
a
,
b
)
{
return
a
+
b
;
}
Pitfall: ASI can unexpectedly break up statements
ASI is also triggered if there is a line terminator after the keyword return
. For example:
// Don't do this
return
{
name
:
'John'
};
ASI turns the preceding into:
return
;
{
name
:
'John'
};
That’s an empty return, followed by a block with the label name
in front of the expression statement 'John'
. After the block, there is an empty statement.
Pitfall: ASI might unexpectedly not be triggered
Sometimes a statement in a new line starts with a token that is allowed as a continuation of the previous statement. Then ASI is not triggered, even though it seems like it should be. For example:
func
()
[
'ul'
,
'ol'
].
forEach
(
function
(
t
)
{
handleTag
(
t
)
})
The square brackets in the second line are interpreted as an index into the result returned by func()
. The comma inside the brackets is interpreted as the comma operator (which returns 'ol'
in this case; see The Comma Operator). Thus, JavaScript sees the previous code as:
func
()[
'ol'
].
forEach
(
function
(
t
)
{
handleTag
(
t
)
});
Legal Identifiers
Identifiers are used for naming things and appear in various syntactic roles in JavaScript. For example, the names of variables and unquoted property keys must be valid identifiers. Identifiers are case sensitive.
The first character of an identifier is one of:
- Any Unicode letter, including Latin letters such as D, Greek letters such as λ, and Cyrillic letters such as Д
-
Dollar sign (
$
) -
Underscore (
_
)
Subsequent characters are:
- Any legal first character
- Any Unicode digit in the Unicode category “Decimal number (Nd)”; this includes European numerals such as 7 and Indic numerals such as ٣
- Various other Unicode marks and punctuations
Examples of legal identifiers:
var
ε
=
0.0001
;
var
строка
=
''
;
var
_tmp
;
var
$foo2
;
Even though this enables you to use a variety of human languages in JavaScript code, I recommend sticking with English, for both identifiers and comments. That ensures that your code is understandable by the largest possible group of people, which is important, given how much code can spread internationally these days.
The following identifiers are reserved words—they are part of the syntax and can’t be used as variable names (including function names and parameter names):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The following three identifiers are not reserved words, but you should treat them as if they were:
|
|
|
Lastly, you should also stay away from the names of standard global variables (see Chapter 23). You can use them for local variables without breaking anything, but your code still becomes confusing.
Note that you can use reserved words as unquoted property keys (as of ECMAScript 5):
> var obj = { function: 'abc' }; > obj.function 'abc'
You can look up the precise rules for identifiers in Mathias Bynens’s blog post “Valid JavaScript variable names”.
Invoking Methods on Number Literals
With method invocations, it is important to distinguish between the floating-point dot and the method invocation dot. Thus, you cannot write 1.toString()
; you must use one of the following alternatives:
1
..
toString
()
1
.
toString
()
// space before dot
(
1
).
toString
()
1.0
.
toString
()
Strict Mode
ECMAScript 5 has a strict mode that results in cleaner JavaScript, with fewer unsafe features, more warnings, and more logical behavior. The normal (nonstrict) mode is sometimes called “sloppy mode.”
Switching on Strict Mode
You switch strict mode on by typing the following line first in your JavaScript file or inside your <script>
element:
'use strict'
;
Note that JavaScript engines that don’t support ECMAScript 5 will simply ignore the preceding statement, as writing strings in this manner (as an expression statement; see Statements) normally does nothing.
You can also switch on strict mode per function. To do so, write your function like this:
function
foo
()
{
'use strict'
;
...
}
This is handy when you are working with a legacy code base where switching on strict mode everywhere may break things.
Strict Mode: Recommended, with Caveats
In general, the changes enabled by strict mode are all for the better. Thus, it is highly recommended to use it for new code you write—simply switch it on at the beginning of a file. There are, however, two caveats:
- Enabling strict mode for existing code may break it
- The code may rely on a feature that is not available anymore, or it may rely on behavior that is different in sloppy mode than in strict mode. Don’t forget that you have the option to add single strict mode functions to files that are in sloppy mode.
- Package with care
- When you concatenate and/or minify files, you have to be careful that strict mode isn’t switched off where it should be switched on or vice versa. Both can break code.
The following sections explain the strict mode features in more detail. You normally don’t need to know them, as you will mostly get more warnings for things that you shouldn’t do anyway.
Variables Must Be Declared in Strict Mode
All variables must be explicitly declared in strict mode. This helps to prevent typos. In sloppy mode, assigning to an undeclared variable creates a global variable:
function
sloppyFunc
()
{
sloppyVar
=
123
;
}
sloppyFunc
();
// creates global variable `sloppyVar`
console
.
log
(
sloppyVar
);
// 123
In strict mode, assigning to an undeclared variable throws an exception:
function
strictFunc
()
{
'use strict'
;
strictVar
=
123
;
}
strictFunc
();
// ReferenceError: strictVar is not defined
Functions in Strict Mode
Strict mode limits function-related features.
Functions must be declared at the top level of a scope
In strict mode, all functions must be declared at the top level of a scope (global scope or directly inside a function). That means that you can’t put a function declaration inside a block. If you do, you get a descriptive SyntaxError
. For example, V8 tells you: “In strict mode code, functions can only be declared at top level or immediately within another function”:
function
strictFunc
()
{
'use strict'
;
if
(
true
)
{
// SyntaxError:
function
nested
()
{
}
}
}
That is something that isn’t useful anyway, because the function is created in the scope of the surrounding function, not “inside” the block.
If you want to work around this limitation, you can create a function inside a block via a variable declaration and a function expression:
function
strictFunc
()
{
'use strict'
;
if
(
true
)
{
// OK:
var
nested
=
function
()
{
};
}
}
Stricter rules for function parameters
The rules for function parameters are less permissive: using the same parameter name twice is forbidden, as are local variables that have the same name as a parameter.
The arguments objects has fewer properties
The arguments
object is simpler in strict mode: the properties arguments.callee
and arguments.caller
have been eliminated, you can’t assign to the variable arguments
, and arguments
does not track changes to parameters (if a parameter changes, the corresponding array element does not change with it). Deprecated features of arguments explains the details.
this is undefined in nonmethod functions
In sloppy mode, the value of this
in nonmethod functions is the global object (window
in browsers; see The Global Object):
function
sloppyFunc
()
{
console
.
log
(
this
===
window
);
// true
}
In strict mode, it is undefined
:
function
strictFunc
()
{
'use strict'
;
console
.
log
(
this
===
undefined
);
// true
}
This is useful for constructors. For example, the following constructor, Point
, is in strict mode:
function
Point
(
x
,
y
)
{
'use strict'
;
this
.
x
=
x
;
this
.
y
=
y
;
}
Due to strict mode, you get a warning when you accidentally forget new
and call it as a function:
> var pt = Point(3, 1); TypeError: Cannot set property 'x' of undefined
In sloppy mode, you don’t get a warning, and global variables x
and y
are created. Consult Tips for Implementing Constructors for details.
Setting and Deleting Immutable Properties Fails with an Exception in Strict Mode
Illegal manipulations of properties throw exceptions in strict mode. For example, attempting to set the value of a read-only property throws an exception, as does attempting to delete a nonconfigurable property. Here is an example of the former:
var
str
=
'abc'
;
function
sloppyFunc
()
{
str
.
length
=
7
;
// no effect, silent failure
console
.
log
(
str
.
length
);
// 3
}
function
strictFunc
()
{
'use strict'
;
str
.
length
=
7
;
// TypeError: Cannot assign to
// read-only property 'length'
}
Unqualified Identifiers Can’t Be Deleted in Strict Mode
In sloppy mode, you can delete a global variable foo
like this:
delete
foo
In strict mode, you get a syntax error whenever you try to delete unqualified identifiers. You can still delete global variables like this:
delete
window
.
foo
;
// browsers
delete
global
.
foo
;
// Node.js
delete
this
.
foo
;
// everywhere (in global scope)
eval() Is Cleaner in Strict Mode
In strict mode, the eval()
function becomes less quirky: variables declared in the evaluated string are not added to the scope surrounding eval()
anymore. For details, consult Evaluating Code Using eval().
Features That Are Forbidden in Strict Mode
Two more JavaScript features are forbidden in strict mode:
-
The
with
statement is not allowed anymore (see The with Statement). You get a syntax error at compile time (when loading the code). No more octal numbers: in sloppy mode, an integer with a leading zero is interpreted as octal (base 8). For example:
> 010 === 8 true
In strict mode, you get a syntax error if you use this kind of literal:
> function f() { 'use strict'; return 010 } SyntaxError: Octal literals are not allowed in strict mode.
Get Speaking JavaScript now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.