在ES2015中切换语句和范围

时间:2016-08-17 16:25:56

标签: javascript node.js ecmascript-6 v8

考虑此ES2015模块以及在节点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

这似乎与我内部不一致。如果switch语句没有创建词法范围,我希望所有hey*变量都是主范围的一部分,并且所有变量都表现一致。如果switch语句确实创建了一个词法范围,我仍然希望它们是一致的,但是case子句中声明的变量的行为就像它们在子范围内一样,而default中的变量子句的行为就像在外部范围内一样。

我的问题是 switch语句中是否涉及任何子作用域,如果是,那么它们的行为方式有哪些细节?

在节点v6.4.0中,行为不同。看起来切换块确实创建了子块范围。

ReferenceError: heyBar is not defined

这似乎更直接易懂。

4 个答案:

答案 0 :(得分:9)

我根本无法重现你的行为。我立即得到一个ReferenceError(节点6.4.0和当前的Firefox):

ReferenceError: heyBar is not defined

这对我来说似乎是正确的行为。带括号DO的AFAIK switch语句创建一个块,因此是块范围实体的词法范围。 case语句本身不会创建自己的块。

如果我们在foo语句中使用switch大小写扩展此示例,它也会抛出ReferenceError

'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

<强>参考

Here is the section in the spec: 13.12.11 Runtime Semantics: Evaluation

5. Let blockEnv be NewDeclarativeEnvironment(oldEnv).
6. Perform BlockDeclarationInstantiation(CaseBlock, blockEnv).

其中CaseBlock是switch case的块语句。

这大致转换为:

在switch语句(switch { <-here-> })的块中创建一个新的块环境,并实例化所有块级声明(例如letconst或块级函数声明)。 / p>

答案 1 :(得分:3)

如果没有ReferenceError: heyDefault is not defined,上面的代码会以严格模式抛出heyBar,否则会抛出ReferenceError: heyBar

switchswitch (...) { ... }语句创建范围,不创建case语句的范围。请参阅the reference

答案 2 :(得分:2)

switch语句的主体创建一个新的块范围。每个单独的case子句或default子句不会自动创建新的块范围。

理解作用域和switch语句的权威参考当然是ES2016 specification。然而,要弄清楚它真正在说什么是一些工作。我将尝试引导您完成我可以遵循的规范。

定义重要条款

首先,switch语句定义为:

SwitchStatement:
    switch ( Expression ) CaseBlock

而且,CaseBlock是这样的:

CaseBlock:
    { CaseClauses }
    { CaseClauses DefaultClause CaseClauses }

CaseClauses:
    CaseClause
    CaseClauses CaseClause

CaseClause:
    case Expression : StatementList

DefaultClause:
    default : StatementList

因此CaseBlockswitch语句的主体(包含所有案例的代码)。

这是我们所期望的,但上面定义的术语CaseBlock对于其他规范参考非常重要。

CaseBlock创建新范围

然后,在13.2.14 Runtime Semantics: BlockDeclarationInstantiation( code, env )中,我们可以看到CaseBlock导致创建一个新的范围。

  

当Block或CaseBlock生产被评估为新的声明   创建环境记录并为每个块作用域绑定   变量,常量,函数,生成器函数或声明的类   该块在环境记录中实例化。

由于CaseBlockswitch语句的主体,这意味着switch语句的主体创建了一个新的块作用域(用于新的let / const声明的容器)

CaseClause添加到现有范围(不创建自己的范围)

然后,在13.12.6 Static Semantics: LexicallyScopedDeclarations中,它描述了解析器在解析时如何收集词法范围的声明。这是规范中的实际文本(解释如下):

  

CaseBlock:{CaseClauses DefaultClause CaseClauses}

     
      
  1. 如果存在第一个CaseClauses,则声明为第一个CaseClauses的LexicalScopedDeclarations。
  2.   
  3. 否则让声明成为新的空列表。
  4.   
  5. 在声明中附加DefaultClause的LexicallyScopedDeclarations的元素。
  6.   
  7. 如果第二个CaseClauses不存在,则返回声明。
  8.   
  9. 否则返回声明附加第二个CaseClauses的LexicallyScopedDeclarations元素的结果。
  10.         

    CaseClauses:CaseClauses CaseClause

         
        
    1. 让声明成为CaseClauses的LexicalScopedDeclarations。
    2.   
    3. 在声明中附加CaseClause的LexicallyScopedDeclarations的元素。
    4.   
    5. 退货声明。
    6.         

      CaseClause:case Expression:StatementList

           
          
      1. 如果存在StatementList,则返回StatementList的LexicallyScopedDeclarations。
      2.   
      3. 否则返回一个新的空列表。
      4.         

        DefaultClause:default:StatementList

             
            
        1. 如果存在StatementList,则返回StatementList的LexicallyScopedDeclarations。
        2.   
        3. 否则返回一个新的空列表。
        4.   

所以,基本上这就是说第一个caseClause创建了一个LexicallyScopedDeclarations对象。然后,随后的每个DefaultClause或CaseClause都附加到该声明对象。这是规范描述如何在范围内构建所有声明。

CaseClause附加到现有的声明对象,它不会创建自己的声明。这意味着它不会创建自己的范围,而是使用包含范围。

当然,您可以在CaseClause中定义一个块,然后该块将是它自己的范围,但是CaseClause不需要块声明,因此默认情况下不会,创造一个新的范围。

运行时语义:评估

然后,在13.12.11 Runtime Semantics: Evaluation

中进一步解释了运行时的工作原理
  

SwitchStatement:switch(Expression)CaseBlock

     
      
  1. 让exprRef成为评估Expression的结果。
  2.   
  3. 让switchValue成为?的GetValue(exprRef)。
  4.   
  5. 让oldEnv成为正在运行的执行上下文的LexicalEnvironment。
  6.   
  7. 让blockEnv为NewDeclarativeEnvironment(oldEnv)。
  8.   
  9. 执行BlockDeclarationInstantiation(CaseBlock,blockEnv)。
  10.   
  11. 将正在运行的执行上下文的LexicalEnvironment设置为blockEnv。
  12.   
  13. 设R是使用参数switchValue执行CaseBlock的CaseBlockEvaluation的结果。
  14.   
  15. 将正在运行的执行上下文的LexicalEnvironment设置为oldEnv。
  16.   
  17. 返回R。
  18.   

此处的操作步骤是步骤4和5,其中为CaseBlock创建了新的块环境。如果您在13.12.11的文本中继续操作,则不会为CaseClause中的CaseBlock创建新的块环境。

  

CaseClause:case Expression:StatementList

     
      
  1. 返回评估StatementList的结果。
  2.   

所以,你有它。 CaseBlock创建一个新的块范围。 CaseClause没有{除非您在CaseClause内明确定义了一个块。)

答案 3 :(得分:1)

使用{ },封装包含范围声明的每个case以创建新的块范围:

const var1 = true;

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