ES2015 中的 switch 语句和作用域
switch statement and scopes in 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 语句没有创建词法作用域,我希望所有 hey*
变量都成为主作用域的一部分并且所有行为都一致。如果 switch 语句确实创建了一个词法作用域,我仍然希望它们是一致的,但是 case
子句中声明的变量表现得就像它们在子作用域中一样,而 default
中的变量子句的行为就像在外部范围内一样。
我的问题是 switch 语句中是否涉及任何子范围,如果有,它们的行为细节是什么?
在节点 v6.4.0 中,行为有所不同。看起来 switch 块确实创建了一个子块作用域。
ReferenceError: heyBar is not defined
这似乎更容易理解。
我完全无法重现你的行为。我立即得到一个 ReferenceError
(节点 6.4.0 和当前的 Firefox):
ReferenceError: heyBar is not defined
这对我来说似乎是正确的行为。 AFAIK switch
带方括号的语句确实会创建一个块,从而为块作用域的实体创建一个词法作用域。 case
语句本身不会创建自己的块。
如果我们在 switch
语句中使用 foo
情况扩展此示例,它也会抛出 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
或块级函数声明) .
如果没有heyBar
,上面的代码在严格模式下抛出ReferenceError: heyDefault is not defined
,否则抛出ReferenceError: heyBar
。
switch
为 switch (...) { ... }
语句创建范围,不创建 case
语句的范围。参见 the reference。
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 导致创建了一个新的作用域。
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.
由于 CaseBlock
是 switch
语句的主体,这意味着 switch
语句的主体创建了一个新的块作用域(新 [=85 的容器) =]声明)。
CaseClause 添加到现有范围(不创建自己的范围)
然后,在 13.12.6 Static Semantics: LexicallyScopedDeclarations 中,它描述了解释器在解析时如何收集词法范围的声明。这是规范中的实际文本(解释如下):
CaseBlock : { CaseClauses DefaultClause CaseClauses }
- If the first CaseClauses is present, let declarations be the LexicallyScopedDeclarations of the first CaseClauses.
- Else let declarations be a new empty List.
- Append to declarations the elements of the LexicallyScopedDeclarations of the DefaultClause.
- If the second CaseClauses is not present, return declarations.
- Else return the result of appending to declarations the elements of the LexicallyScopedDeclarations of the second CaseClauses.
CaseClauses : CaseClauses CaseClause
- Let declarations be LexicallyScopedDeclarations of CaseClauses.
- Append to declarations the elements of the LexicallyScopedDeclarations of CaseClause.
- Return declarations.
CaseClause : case Expression : StatementList
- If the StatementList is present, return the LexicallyScopedDeclarations of StatementList.
- Else return a new empty List.
DefaultClause : default : StatementList
- If the StatementList is present, return the LexicallyScopedDeclarations of StatementList.
- Else return a new empty List.
所以,基本上这就是说第一个 caseClause 创建了一个 LexicallyScopedDeclarations 对象。并且,随后的每个 DefaultClause 或 CaseClause 都会附加到该声明对象。这就是规范描述在一个范围内构建所有声明的方式。
A CaseClause
追加到现有的声明对象,它不会创建自己的。这意味着它不会创建自己的作用域,而是使用包含作用域。
当然,您可以在 CaseClause
中定义一个块,然后该块将成为它自己的范围,但是 CaseClause
不需要块声明,因此它不需要,默认情况下,创建一个新范围。
运行时语义:求值
然后,在 13.12.11 Runtime Semantics: Evaluation
中进一步解释了运行时的工作原理
SwitchStatement: switch(Expression) CaseBlock
- Let exprRef be the result of evaluating Expression.
- Let switchValue be ? GetValue(exprRef).
- Let oldEnv be the running execution context's LexicalEnvironment.
- Let blockEnv be NewDeclarativeEnvironment(oldEnv).
- Perform BlockDeclarationInstantiation(CaseBlock, blockEnv).
- Set the running execution context's LexicalEnvironment to blockEnv.
- Let R be the result of performing CaseBlockEvaluation of CaseBlock with argument switchValue.
- Set the running execution context's LexicalEnvironment to oldEnv.
- Return R.
此处的操作步骤是第 4 步和第 5 步,其中为 CaseBlock
创建了一个新的块环境。如果您继续阅读 13.12.11 的文本,则不会为 CaseBlock
.
中的 CaseClause
创建新的块环境
CaseClause: case Expression: StatementList
- Return the result of evaluating StatementList.
所以,你有它。 CaseBlock
创建一个新的块作用域。 CaseClause
不会(除非您自己在 CaseClause
中明确定义一个块)。
使用{
}
,封装每个包含作用域声明的case
以创建新的块作用域:
const var1 = true;
switch (var1) {
case true: {
const var2 = 0;
break;
}
case false: {
const var2 = 1;
break;
}
default: {
const var2 = 2;
}
}
考虑这个 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 中,行为有所不同。看起来 switch 块确实创建了一个子块作用域。
ReferenceError: heyBar is not defined
这似乎更容易理解。
我完全无法重现你的行为。我立即得到一个 ReferenceError
(节点 6.4.0 和当前的 Firefox):
ReferenceError: heyBar is not defined
这对我来说似乎是正确的行为。 AFAIK switch
带方括号的语句确实会创建一个块,从而为块作用域的实体创建一个词法作用域。 case
语句本身不会创建自己的块。
如果我们在 switch
语句中使用 foo
情况扩展此示例,它也会抛出 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
或块级函数声明) .
如果没有heyBar
,上面的代码在严格模式下抛出ReferenceError: heyDefault is not defined
,否则抛出ReferenceError: heyBar
。
switch
为 switch (...) { ... }
语句创建范围,不创建 case
语句的范围。参见 the reference。
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 导致创建了一个新的作用域。
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.
由于 CaseBlock
是 switch
语句的主体,这意味着 switch
语句的主体创建了一个新的块作用域(新 [=85 的容器) =]声明)。
CaseClause 添加到现有范围(不创建自己的范围)
然后,在 13.12.6 Static Semantics: LexicallyScopedDeclarations 中,它描述了解释器在解析时如何收集词法范围的声明。这是规范中的实际文本(解释如下):
CaseBlock : { CaseClauses DefaultClause CaseClauses }
- If the first CaseClauses is present, let declarations be the LexicallyScopedDeclarations of the first CaseClauses.
- Else let declarations be a new empty List.
- Append to declarations the elements of the LexicallyScopedDeclarations of the DefaultClause.
- If the second CaseClauses is not present, return declarations.
- Else return the result of appending to declarations the elements of the LexicallyScopedDeclarations of the second CaseClauses.
CaseClauses : CaseClauses CaseClause
- Let declarations be LexicallyScopedDeclarations of CaseClauses.
- Append to declarations the elements of the LexicallyScopedDeclarations of CaseClause.
- Return declarations.
CaseClause : case Expression : StatementList
- If the StatementList is present, return the LexicallyScopedDeclarations of StatementList.
- Else return a new empty List.
DefaultClause : default : StatementList
- If the StatementList is present, return the LexicallyScopedDeclarations of StatementList.
- Else return a new empty List.
所以,基本上这就是说第一个 caseClause 创建了一个 LexicallyScopedDeclarations 对象。并且,随后的每个 DefaultClause 或 CaseClause 都会附加到该声明对象。这就是规范描述在一个范围内构建所有声明的方式。
A CaseClause
追加到现有的声明对象,它不会创建自己的。这意味着它不会创建自己的作用域,而是使用包含作用域。
当然,您可以在 CaseClause
中定义一个块,然后该块将成为它自己的范围,但是 CaseClause
不需要块声明,因此它不需要,默认情况下,创建一个新范围。
运行时语义:求值
然后,在 13.12.11 Runtime Semantics: Evaluation
中进一步解释了运行时的工作原理SwitchStatement: switch(Expression) CaseBlock
- Let exprRef be the result of evaluating Expression.
- Let switchValue be ? GetValue(exprRef).
- Let oldEnv be the running execution context's LexicalEnvironment.
- Let blockEnv be NewDeclarativeEnvironment(oldEnv).
- Perform BlockDeclarationInstantiation(CaseBlock, blockEnv).
- Set the running execution context's LexicalEnvironment to blockEnv.
- Let R be the result of performing CaseBlockEvaluation of CaseBlock with argument switchValue.
- Set the running execution context's LexicalEnvironment to oldEnv.
- Return R.
此处的操作步骤是第 4 步和第 5 步,其中为 CaseBlock
创建了一个新的块环境。如果您继续阅读 13.12.11 的文本,则不会为 CaseBlock
.
CaseClause
创建新的块环境
CaseClause: case Expression: StatementList
- Return the result of evaluating StatementList.
所以,你有它。 CaseBlock
创建一个新的块作用域。 CaseClause
不会(除非您自己在 CaseClause
中明确定义一个块)。
使用{
}
,封装每个包含作用域声明的case
以创建新的块作用域:
const var1 = true;
switch (var1) {
case true: {
const var2 = 0;
break;
}
case false: {
const var2 = 1;
break;
}
default: {
const var2 = 2;
}
}