Javascript ES2015中的switch语句和作用域

Javascript ES2015中的switch语句和作用域,javascript,node.js,ecmascript-6,v8,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( o

考虑此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语句中是否涉及子作用域,如果是,它们的行为细节是什么

在NodeV6.4.0中,行为是不同的。看起来开关块确实创建了一个子块作用域

ReferenceError: heyBar is not defined

这似乎更容易理解。

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

这对我来说似乎是正确的行为。带括号的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
参考

其中是开关盒的block语句

这大致可以理解为:


在switch语句的块中创建一个新的块环境(
switch{}
),并实例化所有块级声明(例如
let
const
或块级函数声明)


switch
switch(…){…}
语句创建范围,但不创建
case
语句的范围。请参阅。

开关的主体创建一个新的块作用域。每个单独的
case
子句或
default
子句不会自动创建新的块范围

ReferenceError: heyBar is not defined
当然,理解作用域和
开关
语句的最终参考是。然而,要弄清楚它到底在说什么还需要一些工作。我将尝试引导您了解我可以从该规范中遵循的内容

定义重要术语

首先,switch语句定义如下:

SwitchStatement:
    switch ( Expression ) CaseBlock
一个案例是:

CaseBlock:
    { CaseClauses }
    { CaseClauses DefaultClause CaseClauses }

CaseClauses:
    CaseClause
    CaseClauses CaseClause

CaseClause:
    case Expression : StatementList

DefaultClause:
    default : StatementList
因此,
CaseBlock
switch
语句的主体(包含所有案例的代码)

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

案例块创建新范围

然后,在中,我们可以看到CaseBlock导致创建一个新的范围

当对块或CaseBlock生产进行求值时,将生成一个新的声明性语句 创建环境记录,并对每个块范围进行绑定 中声明的变量、常量、函数、生成器函数或类 块在环境记录中实例化

由于
CaseBlock
switch
语句的主体,这意味着
switch
语句的主体将创建一个新的块范围(用于新let/const声明的容器)

CASE子句添加到现有范围(不创建自己的范围)

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

CaseBlock:{CASECLASSIONS DEFAULTCLASSION CASECLASSION}

  • 如果存在第一个case子句,则将声明设为第一个case子句的词汇范围声明
  • 否则,让声明成为一个新的空列表
  • 将Default子句的LexicalyScopedDeclarations元素附加到声明中
  • 如果第二个case子句不存在,则返回声明
  • Else返回将第二个case子句的lexicalyScopedDeclarations元素附加到声明的结果
  • 案例条款:案例条款

  • 让声明成为case子句的词汇范围声明
  • 将LexicalyScopedDeclarations of Case子句的元素附加到声明中
  • 返回声明
  • CaseClause:case表达式:语句列表

  • 如果存在StatementList,则返回StatementList的LexicalyScopedDeclarations
  • 否则返回一个新的空列表
  • DefaultClause:default:StatementList

  • 如果存在StatementList,则返回StatementList的LexicalyScopedDeclarations
  • 否则返回一个新的空列表
  • 所以,基本上这是说第一个case子句创建了一个lexicalyScopedDeclarations对象。然后,后面的每个DefaultClause或CaseClause都会附加到该声明对象。这就是规范描述在一个范围内构建所有声明的方式

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

    当然,您可以在
    CaseClause
    中定义一个块,然后该块将成为它自己的作用域,但是
    CaseClause
    不需要块声明,因此默认情况下它不会创建新的作用域

    运行时语义:评估

    然后,那里
    CaseBlock:
        { CaseClauses }
        { CaseClauses DefaultClause CaseClauses }
    
    CaseClauses:
        CaseClause
        CaseClauses CaseClause
    
    CaseClause:
        case Expression : StatementList
    
    DefaultClause:
        default : StatementList
    
    const var1 = true;
    
    switch (var1) {
      case true: {
        const var2 = 0;
        break;
      }
      case false: {
        const var2 = 1;
        break;
      }
      default: {
        const var2 = 2;
      }
    }