在ES2015中切换语句和范围

考虑这个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语句没有创build一个词法范围,我希望所有的hey*variables都是主范围的一部分,并且所有的行为都是一致的。 如果switch语句确实创build了一个词法范围,我仍然期望它们是一致的,但case子句中声明的variables的行为就像它们在子范围中一样,而default子句中的variables的行为就像是在外层范围。

我的问题是在switch语句中是否包含任何子范围,如果有,他们的行为细节是什么?

在节点v6.4.0中,行为是不同的。 它看起来像切换块确实创build一个子块范围。

 ReferenceError: heyBar is not defined 

这似乎更直接了解。

我根本无法重现你的行为。 我马上得到一个ReferenceError (节点6.4.0和目前的火狐):

 ReferenceError: heyBar is not defined 

这对我来说似乎是正确的行为。 带括号的AFAIK switch语句DO创build一个块,从而为块范围实体提供一个词法范围。 case陈述本身并不创build自己的块。

如果我们在switch语句中用foo case扩展这个例子,它也会抛出一个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 

参考

以下是规范中的部分:13.12.11运行时语义:评估

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

CaseBlock是开关盒的块语句。

这大致转化为:

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

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

switchswitch (...) { ... }语句创build一个作用域, case语句的作用域不会被创build。 看参考 。

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

理解范围界定和switch语句的权威性参考当然是ES2016规范 。 但是,有些工作要弄清楚它真正的意思。 我会尽力引导你通过我可以遵循的规范。

定义重要条款

首先,switch语句被定义为:

 SwitchStatement: switch ( Expression ) CaseBlock 

而且,一个CaseBlock是这样的:

 CaseBlock: { CaseClauses } { CaseClauses DefaultClause CaseClauses } CaseClauses: CaseClause CaseClauses CaseClause CaseClause: case Expression : StatementList DefaultClause: default : StatementList 

所以CaseBlockswitch语句的主体(包含所有情况的代码)。

这是我们所期待的,但上面定义的术语“ CaseBlock对于其他spec参考很重要。

CaseBlock创build新的范围

然后,在13.2.14运行时语义:BlockDeclarationInstantiation(code,env)中 ,我们可以看到CaseBlock导致一个新的作用域被创build。

当Block或CaseBlock产品被评估时,创build一个新的声明式环境logging,并且在环境logging中实例化在块中声明的每个块范围variables,常量,函数,生成器函数或类的绑定。

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

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

然后,在13.12.6静态语义:LexicallyScopedDeclarations中 ,它描述解释器在parsing时如何收集词法范围的声明。 这里是规范的实际文本(解释如下):

CaseBlock:{CaseClauses DefaultClause CaseClauses}

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

CaseClauses:CaseClauses CaseClause

  1. 让声明成为CaseClauses的LexicallyScopedDeclarations。
  2. 追加到声明CaseClause的LexicallyScopedDeclarations的元素。
  3. 返回声明。

CaseClause:caseexpression式:StatementList

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

DefaultClause:默认:StatementList

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

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

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

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

运行时语义:评估

然后,在13.12.11运行时语义:评估中 , 对运行时的情况有进一步的解释

SwitchStatement:switch(Expression)CaseBlock

  1. 让exprRef是评估expression式的结果。
  2. 让switchValue是? 的GetValue(exprRef)。
  3. 让oldEnv成为正在运行的执行上下文的词法环境。
  4. 让blockEnv成为NewDeclarativeEnvironment(oldEnv)。
  5. 执行BlockDeclarationInstantiation(CaseBlock,blockEnv)。
  6. 将正在运行的执行上下文的LexicalEnvironment设置为blockEnv。
  7. 假设R是使用参数switchValue对CaseBlock执行CaseBlockEvaluation的结果。
  8. 将正在运行的执行上下文的LexicalEnvironment设置为oldEnv。
  9. 返回R.

这里的操作步骤是步骤4和步骤5,为CaseBlock创build一个新的块环境。 如果您在11.12.12的文本中继续, CaseClause中的CaseBlockCaseBlock创build新的块环境。

CaseClause:caseexpression式:StatementList

  1. 返回评估StatementList的结果。

所以你有它。 一个CaseBlock创build一个新的块范围。 CaseClause不会(除非你自己在CaseClause明确定义一个块)。