考虑此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
这似乎更直接易懂。
答案 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-> }
)的块中创建一个新的块环境,并实例化所有块级声明(例如let
,const
或块级函数声明)。 / p>
答案 1 :(得分:3)
如果没有ReferenceError: heyDefault is not defined
,上面的代码会以严格模式抛出heyBar
,否则会抛出ReferenceError: heyBar
。
switch
为switch (...) { ... }
语句创建范围,不创建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
因此CaseBlock
是switch
语句的主体(包含所有案例的代码)。
这是我们所期望的,但上面定义的术语CaseBlock
对于其他规范参考非常重要。
CaseBlock创建新范围
然后,在13.2.14 Runtime Semantics: BlockDeclarationInstantiation( code, env )中,我们可以看到CaseBlock导致创建一个新的范围。
当Block或CaseBlock生产被评估为新的声明 创建环境记录并为每个块作用域绑定 变量,常量,函数,生成器函数或声明的类 该块在环境记录中实例化。
由于CaseBlock
是switch
语句的主体,这意味着switch
语句的主体创建了一个新的块作用域(用于新的let / const声明的容器)
CaseClause添加到现有范围(不创建自己的范围)
然后,在13.12.6 Static Semantics: LexicallyScopedDeclarations中,它描述了解析器在解析时如何收集词法范围的声明。这是规范中的实际文本(解释如下):
CaseBlock:{CaseClauses DefaultClause CaseClauses}
- 如果存在第一个CaseClauses,则声明为第一个CaseClauses的LexicalScopedDeclarations。
- 否则让声明成为新的空列表。
- 在声明中附加DefaultClause的LexicallyScopedDeclarations的元素。
- 如果第二个CaseClauses不存在,则返回声明。
- 否则返回声明附加第二个CaseClauses的LexicallyScopedDeclarations元素的结果。
醇>CaseClauses:CaseClauses CaseClause
- 让声明成为CaseClauses的LexicalScopedDeclarations。
- 在声明中附加CaseClause的LexicallyScopedDeclarations的元素。
- 退货声明。
醇>CaseClause:case Expression:StatementList
- 如果存在StatementList,则返回StatementList的LexicallyScopedDeclarations。
- 否则返回一个新的空列表。
醇>DefaultClause:default:StatementList
- 如果存在StatementList,则返回StatementList的LexicallyScopedDeclarations。
- 否则返回一个新的空列表。
醇>
所以,基本上这就是说第一个caseClause创建了一个LexicallyScopedDeclarations对象。然后,随后的每个DefaultClause或CaseClause都附加到该声明对象。这是规范描述如何在范围内构建所有声明。
CaseClause
附加到现有的声明对象,它不会创建自己的声明。这意味着它不会创建自己的范围,而是使用包含范围。
当然,您可以在CaseClause
中定义一个块,然后该块将是它自己的范围,但是CaseClause
不需要块声明,因此默认情况下不会,创造一个新的范围。
运行时语义:评估
然后,在13.12.11 Runtime Semantics: Evaluation
中进一步解释了运行时的工作原理SwitchStatement:switch(Expression)CaseBlock
- 让exprRef成为评估Expression的结果。
- 让switchValue成为?的GetValue(exprRef)。
- 让oldEnv成为正在运行的执行上下文的LexicalEnvironment。
- 让blockEnv为NewDeclarativeEnvironment(oldEnv)。
- 执行BlockDeclarationInstantiation(CaseBlock,blockEnv)。
- 将正在运行的执行上下文的LexicalEnvironment设置为blockEnv。
- 设R是使用参数switchValue执行CaseBlock的CaseBlockEvaluation的结果。
- 将正在运行的执行上下文的LexicalEnvironment设置为oldEnv。
- 返回R。
醇>
此处的操作步骤是步骤4和5,其中为CaseBlock
创建了新的块环境。如果您在13.12.11的文本中继续操作,则不会为CaseClause
中的CaseBlock
创建新的块环境。
CaseClause:case Expression:StatementList
- 返回评估StatementList的结果。
醇>
所以,你有它。 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;
}
}