Statements are the units of execution. The grammar distinguishes a
Statement from a Declaration, and a block may hold both
(ECMA-262, ECMAScript Language: Statements and Declarations). The semicolons can often
be left out: see Automatic semicolon
insertion.
Overall grammar
Statement:
BlockStatement EmptyStatement ExpressionStatement
VariableStatement IfStatement BreakableStatement
ContinueStatement BreakStatement ReturnStatement
WithStatement LabelledStatement ThrowStatement
TryStatement DebuggerStatement
BreakableStatement:
IterationStatement SwitchStatement
Declaration:
HoistableDeclaration ClassDeclaration LexicalDeclaration
HoistableDeclaration:
FunctionDeclaration GeneratorDeclaration
AsyncFunctionDeclaration AsyncGeneratorDeclaration
Function and class declarations are on their own pages: Functions and Classes. Variable declarations are on Variables and scope.
Blocks and simple statements
| Statement | Form | Notes |
|---|---|---|
| Block | { statements } | Starts a new scope for lexical declarations |
| Empty | ; | Does nothing |
| Expression | expr; | Evaluates and discards the result |
| Debugger | debugger; | A breakpoint, if a debugger is attached. Otherwise it does nothing (ES5) |
An expression statement may not start with {, function,
class, async function or let [, as these would
be ambiguous. To use one as an expression, put it in parentheses.
if
IfStatement:
if ( Expression ) Statement else Statement
if ( Expression ) Statement
The test goes through ToBoolean. An
else belongs to the nearest if that lacks one. A function
declaration cannot be a clause in strict code; in sloppy code, Annex B allows it.
Loops
| Form | Runs |
|---|---|
while (cond) body | Tests before each iteration |
do body while (cond); | Tests after each iteration, so the body runs at least once |
for (init; test; update) body | All three parts may be empty; an empty test is always true |
for (x in obj) body | Over string keys of enumerable properties, including inherited ones |
for (x of iterable) body | Over the values of an iterable (ES2015) |
for await (x of iterable) body | Over an async iterable, in async code (ES2018) |
In a for head, init may be an expression, or a
var, let or const declaration. A
let is copied
for each iteration.
for…in
The loop goes through the enumerable string-keyed properties of the object, then of its prototype, and so on (EnumerateObjectProperties).
- Symbol keys are skipped.
- An inherited property is skipped if a property of the same name has already been seen, even if that one was non-enumerable.
- A property deleted before it is reached is not visited. One added during the loop may or may not be.
- A
nullorundefinedoperand makes the loop do nothing. - The order is specified where no prototype or exotic object changes things: own integer-like keys in ascending order, then other strings in creation order, then the prototype’s.
Use for…of with Object.keys(obj) to ignore inherited
properties.
for…of
The loop uses the iteration
protocol. Leaving the loop early, by break, return or a
throw, calls the iterator’s return method, if it has one.
for (const [key, value] of new Map([['a', 1]])) {
console.log(key, value);
}
for (let of of [1, 2]) { } // a variable named "of" is allowed
switch
SwitchStatement:
switch ( Expression ) CaseBlock
CaseBlock:
{ [CaseClauses] }
{ [CaseClauses] DefaultClause [CaseClauses] }
CaseClause:
case Expression : [StatementList]
DefaultClause:
default : [StatementList]
switch (value) {
case 1:
case 2:
handleLow();
break;
case getSpecial():
handleSpecial();
break;
default:
handleOther();
}
- Each case expression is compared with
===, evaluated in order. It need not be a constant. - Control falls through to the next clause unless
breakor some other jump occurs. defaultruns if no case matches, and may be anywhere in the block.- The whole case block is one scope, so a
letorconstunder onecaseis in scope in the others, and may be in its dead zone there. Add braces to give a clause its own scope.
Labels and jumps
LabelledStatement:
LabelIdentifier : LabelledItem
ContinueStatement:
continue ;
continue [no LineTerminator here] LabelIdentifier ;
BreakStatement:
break ;
break [no LineTerminator here] LabelIdentifier ;
ReturnStatement:
return ;
return [no LineTerminator here] Expression ;
outer:
for (const row of grid) {
for (const cell of row) {
if (cell.done) continue outer;
if (cell.bad) break outer;
}
}
breakwith no label leaves the nearest loop orswitch.continuewith no label starts the next iteration of the nearest loop. A labelledcontinuemust name a loop.- A labelled
breakmay leave any labelled statement, including a block. returnis allowed only in function bodies. Its value isundefinedif none is given.
throw and try
ThrowStatement:
throw [no LineTerminator here] Expression ;
TryStatement:
try Block Catch
try Block Finally
try Block Catch Finally
Catch:
catch ( CatchParameter ) Block
catch Block
CatchParameter:
BindingIdentifier
BindingPattern
try {
risky();
} catch (error) { // the binding may be left out (ES2019)
if (!(error instanceof TypeError)) throw error;
} finally {
cleanUp(); // always runs
}
- Any value can be thrown, not just an
Error. - A
finallyblock runs however thetryends. A jump or a throw infinallyreplaces the original result, so areturnthere overrides one fromtry. - The
catchparameter may be a destructuring pattern, and its binding is scoped to the clause.
with
WithStatement:
with ( Expression ) Statement
with puts an object on the scope chain for its body, so unqualified names
look up properties first. It is a syntax error in
strict code, and is therefore not allowed in
modules or classes. Symbol.unscopables hides names from it.
using declarations
The current draft (expected in ES2027) adds declarations that release a resource when
the scope ends. A using declaration is like a const with
clean-up attached.
UsingDeclaration:
using [no LineTerminator here] BindingList ;
AwaitUsingDeclaration:
await [no LineTerminator here] using [no LineTerminator here] BindingList ;
async function work() {
using file = openFile(); // file[Symbol.dispose]() on exit
await using db = await connect(); // await db[Symbol.asyncDispose]() on exit
return file.read();
}
- The binding must be a simple identifier, not a pattern.
- The value must be an object with the method, or
nullorundefined, which are skipped. await usingcallsSymbol.asyncDisposeand falls back toSymbol.dispose. It needs an async context.- Not allowed at the top level of a script, nor directly in a
caseclause. Allowed at a module’s top level. - A
forhead can use them:for (using x of list). The spec’s grammar has a lookahead so thatfor (using of list)keeps its old meaning. - If a disposer throws while another error is in flight, a
SuppressedErrorholds both.