Skip to content Skip to sidebar Skip to footer

Switch Statement And Scopes In Es2015

Consider this ES2015 module and the behavior when run in node v4.4.5. 'use strict' const outer = 1 switch ('foo') { case 'bar': const heyBar = 'HEY_BAR' break case 'b

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 }

  1. If the first CaseClauses is present, let declarations be the LexicallyScopedDeclarations of the first CaseClauses.
  2. Else let declarations be a new empty List.
  3. Append to declarations the elements of the LexicallyScopedDeclarations of the DefaultClause.
  4. If the second CaseClauses is not present, return declarations.
  5. Else return the result of appending to declarations the elements of the LexicallyScopedDeclarations of the second CaseClauses.

CaseClauses : CaseClauses CaseClause

  1. Let declarations be LexicallyScopedDeclarations of CaseClauses.
  2. Append to declarations the elements of the LexicallyScopedDeclarations of CaseClause.
  3. Return declarations.

CaseClause : case Expression : StatementList

  1. If the StatementList is present, return the LexicallyScopedDeclarations of StatementList.
  2. Else return a new empty List.

DefaultClause : default : StatementList

  1. If the StatementList is present, return the LexicallyScopedDeclarations of StatementList.
  2. 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

  1. Let exprRef be the result of evaluating Expression.
  2. Let switchValue be ? GetValue(exprRef).
  3. Let oldEnv be the running execution context's LexicalEnvironment.
  4. Let blockEnv be NewDeclarativeEnvironment(oldEnv).
  5. Perform BlockDeclarationInstantiation(CaseBlock, blockEnv).
  6. Set the running execution context's LexicalEnvironment to blockEnv.
  7. Let R be the result of performing CaseBlockEvaluation of CaseBlock with argument switchValue.
  8. Set the running execution context's LexicalEnvironment to oldEnv.
  9. 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

  1. 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"