switch statement and scopes in ES2015

耗尽温柔 提交于 2021-02-08 23:45:47

问题


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 'baz':
    const heyBaz = 'HEY_BAZ'
    break
  default:
    const heyDefault = 'HEY_DEFAULT'
}
console.log(
  outer, // 1, makes sense, same top-level scope
  heyBar, // undefined. huh? I thought switch did NOT create a child scope
  heyBaz, // undefined. huh? I thought switch did NOT create a child scope
  heyDefault) // 'HEY_DEFAULT' makes sense

This seems internally inconsistent to me. If the switch statement did NOT create a lexical scope, I would expect all the hey* variables to be part of the main scope and to all behave consistently. If the switch statement did create a lexical scope, I would still expect them to be consistent, but the variables declared in the case clauses behave like they are in a child scope, whereas the variables in the default clause behaves like it is in the outer scope.

My question is are there any child scopes involved in a switch statement, and if so what are the details of how they behave?

In node v6.4.0, the behavior is different. It looks like the switch block does indeed create a child block scope.

ReferenceError: heyBar is not defined

And that seems a lot more straightforward to understand.


回答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 is not 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 = 1

switch ('foo') {
  case 'bar':
    const heyBar = 'HEY_BAR'
    break
  case 'baz':
    const heyBaz = 'HEY_BAZ'
    break
  case 'foo':
    const heyFoo = 'HEY_FOO'
    break
  default:
    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).




回答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.




回答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 }
    { CaseClauses DefaultClause CaseClauses }

CaseClauses:
    CaseClause
    CaseClauses CaseClause

CaseClause:
    case Expression : StatementList

DefaultClause:
    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).




回答4:


With { }, encapsulate each case that contains a scoped declaration to create a new block scope:

const var1 = true;

switch (var1) {
  case true: {
    const var2 = 0;
    break;
  }
  case false: {
    const var2 = 1;
    break;
  }
  default: {
    const var2 = 2;
  }
}


来源:https://stackoverflow.com/questions/39001827/switch-statement-and-scopes-in-es2015

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!