Fundamentals of Web Application Development · DRAFTFreeman

Grammar & Syntax

JavaScript was created from the beginning to intentionally bear a strong syntactic resemblance to Java (and their common older cousin C++). The way that code is structured should appear familiar to anyone with exposure to those languages.

Characters

JavaScript programs are made up of tokens of UTF-16 characters. Extra whitespace between tokens is ignored, including spaces, tabs, and often line breaks. This allows code to be authored in a flexible way – for example, many developers choose to break up expressions across multiple lines with varying indentation in order to increase legibility.

let foo = { a: 1, b: 2, c: 3 };

// is interpreted exactly like

let foo = {
  a: 1,
  b: 2,
  c: 3,
};

Comments

There are two kinds of comments in JavaScript: single-line comments start with with a double-backslash (//) and go to the end of the line, and multi-line comments start with /* and end with */.

let a = 42; // here is a single-line comment

// here is another single-line comment

/* here is a
comment spanning
multiple lines
*/

let b = /* comment */ 'abc';

Identifiers

An identifier is a name given to a variable or function. Identifiers in JavaScript may be composed of Unicode alphanumeric characters, the dollar sign ($), and the underscore (_). The first character cannot be a numeric digit. Conventionally, identifiers are writtenInCamelCase.

let myVariable = 'Hello, world';

let someThing123;

let $ = 42;

function _my_function_name_() {
  /* ... */
}

// non-ASCII Unicode alpha characters

const π = Math.PI;

let ಠ_ಠ;

let こんにちは = 'hello';

let जावास्क्रिप्ट = 'javascript';

JavaScript is case-sensitive, which means that foo and Foo are two distinct, unrelated identifiers.

The following are reserved words that cannot be used as identifiers:

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, yield

Expressions

An expression is a unit of code that produces a value when evaluated. It may include immediate literal values, identifier (variable name) lookups, operations, or even function calls.

// Assume each line below is evalulated independently.
// The produced values for each are shown in comments.

1 + 1 // 2

5.0 // 5

'Hello, world' // "Hello, world"

4 < 5 // true

Math.cos(0) // 1

Expressions may be nested and combined into larger compound expressions in order to produce an aggregate value.

// Assume each line below is evalulated independently.

1 + 2 + 3 // 6

4 < 5 && (8 >= (3 * 3 * Math.cos(0))) // false

x = 123 // 123

typeof 42 // "number"

typeof (typeof 42) // "string"

Some expressions may have side effects, which means that their evaluation may alter state in the program. For example, assignment operators (such as =) bind values to variables. Calling a function will cause it to execute, which may itself also cause side effects.

Operators

The order in which operations are evaluated depends on their precedence: operators with a higher precedence will be evaluated first. Adjacent operators with the same precedence will be evaluated according to their associativity, left-to-right or right-to-left. Expressions can be explicitly grouped in parentheses () in order to control the order of evaluation.

// Assume each line below is evaluated independently.
// Comments show a representation of the evalution steps.

   1 + 2 * 3
// 1 + (2 * 3)
// 1 + 6
// 7

   (1 + 2) * 3
// 3 * 3
// 9

   typeof 'abc' == 'string'
// "string" == "string"
// true

   typeof ('abc' == 'string')
// typeof false
// "boolean"
JavaScript Operators^[Adapted from [Operator precedence](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table) by Mozilla Contributors, licensed under CC-BY-SA 2.5.]
Precedence Operator type Associativity Individual operators
20 Grouping n/a ( … )
19 Member Access left-to-right … . …
Computed Member Access left-to-right … [ … ]
new (with argument list) n/a new … ( … )
Function Call left-to-right … ( … )
18 new (without argument list) right-to-left new …
17 Postfix Increment n/a … ++
Postfix Decrement … ‐‐
16 Logical NOT right-to-left ! …
Bitwise NOT ~ …
Unary Plus + …
Unary Negation - …
Prefix Increment ++ …
Prefix Decrement ‐‐ …
typeof typeof …
void void …
delete delete …
await await …
15 Exponentiation right-to-left … ** …
14 Multiplication left-to-right … * …
Division … / …
Remainder (modulo) … % …
13 Addition left-to-right … + …
Subtraction … - …
12 Bitwise Left Shift left-to-right … << …
Bitwise Right Shift … >> …
Bitwise Unsigned Right Shift … >>> …
11 Less Than left-to-right … < …
Less Than Or Equal … <= …
Greater Than … > …
Greater Than Or Equal … >= …
in … in …
instanceof … instanceof …
10 Equality left-to-right … == …
Inequality … != …
Strict Equality … === …
Strict Inequality … !== …
9 Bitwise AND left-to-right … & …
8 Bitwise XOR left-to-right … ^ …
7 Bitwise OR left-to-right … | …
6 Logical AND left-to-right … && …
5 Logical OR left-to-right … || …
4 Conditional right-to-left … ? … : …
3 Assignment right-to-left … = …
… += …
… -= …
… **= …
… *= …
… /= …
… %= …
… <<= …
… >>= …
… >>>= …
… &= …
… ^= …
… |= …
2 yield right-to-left yield …
yield* yield* …
1 Comma / Sequence left-to-right … , …

Statements

Statements include variable and function declarations, instructions for control flow, and blocks.

Variables and functions can be declared using the declaration statements let, const, and var (we will explore the differences between these declarations in Chapter ):

var foo;
let bar;
const baz;

let thing1, thing2, thing3; // multiple named variable declarations

function qux() {
  // function body
}

The program control flow statements return, throw, break, and continue, and control structures such as if..else, do..while, try..catch, and for loops, are all statements.

let x = 0;

// `for`, `if`, and `else` control statements are each followed by a statement

for (let i = 0; i < 5; i++) x = x + 1;

if (condition) x = x * 2;
else return 456;

A block statement encloses multiple statements in curly braces ({}) and can itself be used in the place of a single statement.

for (let i = 0; i < 5; i++) {
  console.log(x);
  x = x + i;
  if (x > y) break;
}

if (condition) {
  console.log('condition was truthy');
  x = x * 2;
} else {
  console.log('condition was falsy');
  return 456;
}

If an expression is used in a context where a statement is expected, it is considered an expression statement. However, the reverse is not true: statements are not allowed in an expression context.

let value = if (condition) { x = 1; }; // SyntaxError: Unexpected token if

Semicolons

Like many other languages, JavaScript uses semicolons to end statements.

The exception to this is for block statements that end in a closing curly bracket, like control structures (if, for, etc.) or function declarations:

if (condition) {
  // ...
} // no semi-colon

Automatic Semicolon Insertion (ASI)

In some cases, the end of statements may be inferred. The spec defines a mechanism called automatic semicolon insertion which will add semicolons at the end of a line or after a closing curly bracket } if the next token is not otherwise allowed in the grammar.

function foo() {
  let x = 2
  {
    x += 3
    x *= 7 + 3
  }
  return x
}

foo(); // 50

Some authors prefer to write JavaScript with as few semicolons as possible. While this may still produce working code, ASI has nuanced rules in many cases and may result in unwanted behavior if not accounted for.

Lines starting with an opening parenthesis ( or square bracket [ may be interpreted as part of the expression on the previous line. For example,

a = b + c
(d + e)

// is interpreted as

a = b + c(d + e); // calling c as a function

// and

a = b + c
[1,2,3].length

// is interpreted as

a = b + c[1,2,3].length; // same as a = b + c[3].length

Line breaks are not allowed between continue, break, return, throw, or yield and their succeeding token, if any.

function foo() {
  return
    2 + 2;
}

// foo will always return undefined!
// ASI puts a semicolon after the return token:

function foo() {
  return;
  2 + 2; // this line never reached
}

// The expression following a return may span multiple lines,
// but it must start on the same line

function foo() {
  return (
    2 + 2
  ); // this works!
}