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:

  • Booleans:

    true
    false
  • Numbers:

    1023
    7.851
  • Strings:

    'hello'
    "hello"
  • Plain objects:

    {
        firstName: 'Jane',
        lastName: 'Doe'
    }
  • Arrays:

    [ 'apple', 'banana', 'cherry' ]

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 call bar(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 not do-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):

arguments

break

case

catch

class

const

continue

debugger

default

delete

do

else

enum

export

extends

false

finally

for

function

if

implements

import

in

instanceof

interface

let

new

null

package

private

protected

public

return

static

super

switch

this

throw

true

try

typeof

var

void

while

The following three identifiers are not reserved words, but you should treat them as if they were:

Infinity

NaN

undefined

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.

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.



[8] To keep things simple, I’m pretending that declarations are statements.

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.