在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一个新的块环境,并实例化所有块级声明(如let
, const
或块级函数声明)。
上面的代码抛出了ReferenceError: heyDefault is not defined
如果没有heyBar
, heyBar
在严格模式下ReferenceError: heyDefault is not defined
,否则抛出ReferenceError: heyBar
。
switch
为switch (...) { ... }
语句创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
所以CaseBlock
是switch
语句的主体(包含所有情况的代码)。
这是我们所期待的,但上面定义的术语“ CaseBlock
对于其他spec参考很重要。
CaseBlock创build新的范围
然后,在13.2.14运行时语义:BlockDeclarationInstantiation(code,env)中 ,我们可以看到CaseBlock导致一个新的作用域被创build。
当Block或CaseBlock产品被评估时,创build一个新的声明式环境logging,并且在环境logging中实例化在块中声明的每个块范围variables,常量,函数,生成器函数或类的绑定。
由于CaseBlock
是switch
语句的主体,这意味着switch
语句的主体将创build一个新的块作用域(新的let / const声明的容器)。
CaseClause添加到现有范围(不创build自己的范围)
然后,在13.12.6静态语义:LexicallyScopedDeclarations中 ,它描述解释器在parsing时如何收集词法范围的声明。 这里是规范的实际文本(解释如下):
CaseBlock:{CaseClauses DefaultClause CaseClauses}
- 如果第一个CaseClauses存在,让声明为第一个CaseClauses的LexicalScopedDeclarations。
- 否则让声明成为一个新的空的列表。
- 追加到声明DefaultClause的LexicallyScopedDeclarations的元素。
- 如果第二个CaseClauses不存在,则返回声明。
- 否则返回声明第二个CaseClauses的LexicallyScopedDeclarations的元素的结果。
CaseClauses:CaseClauses CaseClause
- 让声明成为CaseClauses的LexicallyScopedDeclarations。
- 追加到声明CaseClause的LexicallyScopedDeclarations的元素。
- 返回声明。
CaseClause:caseexpression式:StatementList
- 如果StatementList存在,则返回StatementList的LexicallyScopedDeclarations。
- 否则返回一个新的空列表。
DefaultClause:默认:StatementList
- 如果StatementList存在,则返回StatementList的LexicallyScopedDeclarations。
- 否则返回一个新的空列表。
所以,基本上这是说,第一个caseClause创build一个LexicallyScopedDeclarations对象。 然后,每个DefaultClause或CaseClause将附加到该声明对象。 这是规范描述如何在范围内build立所有的声明。
一个CaseClause
附加到现有的声明对象,它不会创build它自己的。 这意味着它不会创build自己的范围,而是使用包含范围。
当然,您可以在CaseClause
定义一个块,然后该块将成为它自己的范围,但是CaseClause
不需要块声明,因此默认情况下它不创build新的范围。
运行时语义:评估
然后,在13.12.11运行时语义:评估中 , 对运行时的情况有进一步的解释
SwitchStatement:switch(Expression)CaseBlock
- 让exprRef是评估expression式的结果。
- 让switchValue是? 的GetValue(exprRef)。
- 让oldEnv成为正在运行的执行上下文的词法环境。
- 让blockEnv成为NewDeclarativeEnvironment(oldEnv)。
- 执行BlockDeclarationInstantiation(CaseBlock,blockEnv)。
- 将正在运行的执行上下文的LexicalEnvironment设置为blockEnv。
- 假设R是使用参数switchValue对CaseBlock执行CaseBlockEvaluation的结果。
- 将正在运行的执行上下文的LexicalEnvironment设置为oldEnv。
- 返回R.
这里的操作步骤是步骤4和步骤5,为CaseBlock
创build一个新的块环境。 如果您在11.12.12的文本中继续, CaseClause
中的CaseBlock
将CaseBlock
创build新的块环境。
CaseClause:caseexpression式:StatementList
- 返回评估StatementList的结果。
所以你有它。 一个CaseBlock
创build一个新的块范围。 CaseClause
不会(除非你自己在CaseClause
明确定义一个块)。