Switch Statement And Scopes In Es2015
Solution 1:
I can't reproduce your behavior at all. I immediately get a ReferenceError
(Node 6.4.0 and current Firefox for that matter):
ReferenceError: heyBar isnot defined
Which seems like the correct behavior to me. AFAIK switch
statements with brackets DO create a block, and thus a lexical scope for block-scoped entities. The case
statements themselves do not create their own blocks.
If we expand this example with a foo
case in the switch
statement, it throws a ReferenceError
as well:
'use strict'const outer = 1switch ('foo') {
case'bar':
const heyBar = 'HEY_BAR'breakcase'baz':
const heyBaz = 'HEY_BAZ'breakcase'foo':
const heyFoo = 'HEY_FOO'breakdefault:
const heyDefault = 'HEY_DEFAULT'
}
console.log(
outer,
heyFoo,
heyBar,
heyBaz,
heyDefault) // ReferenceError: heyFoo is not defined
Reference
Here is the section in the spec: 13.12.11 Runtime Semantics: Evaluation
5. Let blockEnv be NewDeclarativeEnvironment(oldEnv).
6. Perform BlockDeclarationInstantiation(CaseBlock, blockEnv).
Where CaseBlock
is the block statement of the switch case.
This roughly translates to:
Create a new block environment in the block of the switch statement (switch { <-here-> }
) and instantiate all block level declarations (such as let
, const
or block level function declarations).
Solution 2:
The code above throws ReferenceError: heyDefault is not defined
in strict mode if there's no heyBar
, and it throws ReferenceError: heyBar
otherwise.
switch
creates a scope for switch (...) { ... }
statement, scopes for case
statements are not created. See the reference.
Solution 3:
The body of a switch
statement creates a new block scope. Each individual case
clause or default
clause do not automatically create a new block scope.
The definitive reference for understanding scoping and the switch
statement is, of course, the ES2016 specification. It is, however, some work to figure out what it's really saying. I will try to walk you through what I can follow from that specification.
Define Important Terms
First off, a switch statement is defined to be this:
SwitchStatement:
switch ( Expression ) CaseBlock
And, a CaseBlock is this:
CaseBlock:
{ CaseClauses }
{ CaseClausesDefaultClauseCaseClauses }
CaseClauses:CaseClauseCaseClausesCaseClauseCaseClause:case Expression :StatementListDefaultClause:default :StatementList
So a CaseBlock
is the body of the switch
statement (the code that includes all the cases).
This is what we are expecting, but the term CaseBlock
as defined above is important for other spec references.
CaseBlock creates new Scope
Then, in 13.2.14 Runtime Semantics: BlockDeclarationInstantiation( code, env ), we can see that a CaseBlock causes a new scope to be created.
When a Block or CaseBlock production is evaluated a new declarative Environment Record is created and bindings for each block scoped variable, constant, function, generator function, or class declared in the block are instantiated in the Environment Record.
Since the CaseBlock
is the body of the switch
statement, this means that the body of the switch
statement creates one new block scope (a container for new let/const declarations).
CaseClause Adds to Existing Scope (does not create its own scope)
Then, in 13.12.6 Static Semantics: LexicallyScopedDeclarations, it describes how lexically scoped declarations are collected by the interpreter at parse time. Here's the actual text from the spec (explanation will follow):
CaseBlock : { CaseClauses DefaultClause CaseClauses }
- If the first CaseClauses is present, let declarations be the LexicallyScopedDeclarations of the first CaseClauses.
- Else let declarations be a new empty List.
- Append to declarations the elements of the LexicallyScopedDeclarations of the DefaultClause.
- If the second CaseClauses is not present, return declarations.
- Else return the result of appending to declarations the elements of the LexicallyScopedDeclarations of the second CaseClauses.
CaseClauses : CaseClauses CaseClause
- Let declarations be LexicallyScopedDeclarations of CaseClauses.
- Append to declarations the elements of the LexicallyScopedDeclarations of CaseClause.
- Return declarations.
CaseClause : case Expression : StatementList
- If the StatementList is present, return the LexicallyScopedDeclarations of StatementList.
- Else return a new empty List.
DefaultClause : default : StatementList
- If the StatementList is present, return the LexicallyScopedDeclarations of StatementList.
- Else return a new empty List.
So, basically what this is saying is that the first caseClause creates a LexicallyScopedDeclarations object. And, then each DefaultClause or CaseClause that follows appends to that declarations object. This is how the spec describes building up all the declarations within a scope.
A CaseClause
appends to the existing declarations object, it does not create its own. That means it does not create its own scope, but instead uses the containing scope.
You could, of course, define a block within a CaseClause
and that block would then be its own scope, but a CaseClause
does not require a block declaration so thus it does not, by default, create a new scope.
Runtime Semantics: Evaluation
Then, there is further explanation about how things work at runtime in 13.12.11 Runtime Semantics: Evaluation
SwitchStatement: switch(Expression) CaseBlock
- Let exprRef be the result of evaluating Expression.
- Let switchValue be ? GetValue(exprRef).
- Let oldEnv be the running execution context's LexicalEnvironment.
- Let blockEnv be NewDeclarativeEnvironment(oldEnv).
- Perform BlockDeclarationInstantiation(CaseBlock, blockEnv).
- Set the running execution context's LexicalEnvironment to blockEnv.
- Let R be the result of performing CaseBlockEvaluation of CaseBlock with argument switchValue.
- Set the running execution context's LexicalEnvironment to oldEnv.
- Return R.
The operative step here are steps 4 and 5 where a new block environment is created for the CaseBlock
. If you follow on in the text of 13.12.11, no new block environment is created for a CaseClause
within the CaseBlock
.
CaseClause: case Expression: StatementList
- Return the result of evaluating StatementList.
So, there you have it. A CaseBlock
creates a new block scope. A CaseClause
does not (unless you explicitly define a block yourself within the CaseClause
).
Solution 4:
With {
}
, encapsulate each case
that contains a scoped declaration to create a new block scope:
const var1 = true;
switch (var1) {
casetrue: {
const var2 = 0;
break;
}
casefalse: {
const var2 = 1;
break;
}
default: {
const var2 = 2;
}
}
Post a Comment for "Switch Statement And Scopes In Es2015"