Description
A switch
statement first evaluates its expression. It then looks for the first case
clause whose expression evaluates to the same value as the result of the input expression (using the strict comparison, ===
) and transfers control to that clause, executing all statements following that clause.
The clause values are only evaluated when necessary - if a match is already found, subsequent case
clause values will not be evaluated, even when they will be visited by fall-through.
switch (undefined) { case console.log(1): case console.log(2): } // Only logs 1
If no matching case
clause is found, the program looks for the optional default
clause, and if found, transfers control to that clause, executing statements following that clause. If no default
clause is found, the program continues execution at the statement following the end of switch
. By convention, the default
clause is the last clause, but it does not need to be so. A switch
statement may only have one default
clause; multiple default
clauses will result in a SyntaxError
.
Breaking and fall-through
You can use the break
statement within a switch
statement's body to break out early, often when all statements between two case
clauses have been executed. Execution will continue at the first statement following switch
.
If break
is omitted, execution will proceed to the next case
clause, even to the default
clause, regardless of whether the value of that clause matches. This behavior is called "fall-through".
const foo = 0; switch (foo) { case -1: console.log('negative 1'); break; case 0: // Value of foo matches this criteria; execution starts from here console.log(0); // Forgotten break! Execution falls through case 1: // no break statement in 'case 0:' so this case will run as well console.log(1); break; // Break encountered; will not continue into 'case 2:' case 2: console.log(2); break; default: console.log('default'); } // Logs "0" and "1"
You can use other control-flow statements to replace break
, such as a return
statement.
Lexical scoping
The case
and default
clauses are like labels: they indicate possible places that control flow may jump to. However, they don't create lexical scopes themselves (neither do they automatically break out - as demonstrated above). For example:
const action = 'say_hello'; switch (action) { case 'say_hello': const message = 'hello'; console.log(message); break; case 'say_hi': const message = 'hi'; console.log(message); break; default: console.log('Empty action received.'); }
This example will output the error "Uncaught SyntaxError: Identifier 'message' has already been declared", because the first const message = 'hello';
conflicts with the second const message = 'hi';
declaration, even when they're within their own separate case clauses. Ultimately, this is due to both const
declarations being within the same block scope created by the switch
body.
To fix this, whenever you need to use let
or const
declarations in a case
clause, wrap it in a block.
const action = 'say_hello'; switch (action) { case 'say_hello': { // added brackets const message = 'hello'; console.log(message); break; } // added brackets case 'say_hi': { // added brackets const message = 'hi'; console.log(message); break; } // added brackets default: { // added brackets console.log('Empty action received.'); } // added brackets }
This code will now output hello
in the console as it should, without any errors.