You-dont-know-js: Scopes & Closures Ch. 6 - Switch Statement Scoping

Created on 25 May 2020  路  11Comments  路  Source: getify/You-Dont-Know-JS

Please type "I already searched for this issue":
I already searched for this issue.

Edition: (1st or 2nd)
2nd

Book Title:
Scope & Closures

Chapter:
Chapter 6: Limiting Scope Exposure

Section Title:
Scoping with Blocks

Problem:

The { .. } curly-brace pair on a switch statement (around the set of case clauses) does not define a block/scope.

As best I can tell, the switch statement curly-braces referred to do create a scope with const or let.

For example, with var, foo outputs 'foo' as expected.

switch (true) {
  case true:
    var foo = 'foo';
}
console.log(foo);

However, with let, logging foo produces a reference error. (Not expected if these braces aren't creating a scope)

switch (true) {
  case true:
    let foo = 'foo';
}
console.log(foo);

This came up in our last online Code Club discussion (which I believe you have visited previously). Thanks so much for providing this resource!

errata

Most helpful comment

If case statements implicitly create a scope, I'd expect this to produce a reference error (via fallthrough):

switch (true) {
   case true:
      let foo = 42;
    case 'anyvalue':
      console.log(foo);
}

Unexpectedly however, it ouputs 42. (Which unfortunately doesn't satisfy my ultimate questions of life, the universe, and everything in this case :laughing:)

All 11 comments

The cited quote is asserting that the outer pair of { .. } attached to the switch itself does not create a scope. A let / const declaration in a case clause block-scopes the declaration to that case clause specifically, but the outer switch statement itself is still not a block of scope.

If there are no { .. } surrounding the case clause's list of statements, then this list of statements itself acts like a block-scope (though not technically a block).

IOW, this is not a block-scope (and also not syntactically allowed):

switch (true) {
   // not block scoped to the switch (but also syntactically disallowed)
   let foo = 42;
   console.log(foo);
}

But this is a block-scope:

switch (true) {
   case true:
      // the scope here is the `case` clause, specifically its statement list, even
      // without any explicit { }
      let foo = 42;
      console.log(foo);
}

If case statements implicitly create a scope, I'd expect this to produce a reference error (via fallthrough):

switch (true) {
   case true:
      let foo = 42;
    case 'anyvalue':
      console.log(foo);
}

Unexpectedly however, it ouputs 42. (Which unfortunately doesn't satisfy my ultimate questions of life, the universe, and everything in this case :laughing:)

Welp... I'm stumped. That's not what I understood.

According to the spec, the { } around the case statements are not a block -- well, sorta, it's a special CaseBlock entity -- and the parsed AST does not have a "block" element for this node... but nevertheless a scope is created. So I was both correct and incorrect in the same sentence. Bonkers that TC39 chose to make that nuance. Shrugs. Definitely don't ever do that in a program, it's a terrible idea for confusion sake.

Ok. Appreciate you taking the time to look into this. Thanks again for all your work on the book and for making it available online! If you ever have time to join us again some time let us know!

@getify this project seems like fun. I want to contribute. How do i go about that? The documentation is not quite self sufficient

@KarenEfereyan This isn't really a "project" per se. It's a series of books I wrote (and am writing). Contributors are primarily those who find typos that my copyeditor may have missed, or occasionally find technical mistakes (such as this thread).

As for contributing in that capacity, see these guidelines.

If case statements implicitly create a scope, I'd expect this to produce a reference error (via fallthrough):

switch (true) {
   case true:
      let foo = 42;
    case 'anyvalue':
      console.log(foo);
}

Unexpectedly however, it ouputs 42. (Which unfortunately doesn't satisfy my ultimate questions of life, the universe, and everything in this case :laughing:)

Switch is a structural directive which natively defines how it handles execution - matched cases should execute. The case items themselves should be seen as they are all in one scope. This means each case acts like a piece of code that will execute when the it case matches the switch clause, all cases are in a transient scope under the switch directive, so ideally the switch can be called a block and not a scope.

The case items themselves should be seen as they are all in one scope.

This isn't entirely true, since if you put { .. } around the statements of a single case clause, that is a nested block-scope.

@KarenEfereyan This isn't really a "project" per se. It's a series of books I wrote (and am writing). Contributors are primarily those who find typos that my copyeditor may have missed, or occasionally find technical mistakes (such as this thread).

As for contributing in that capacity, see these guidelines.

Okay thanks

The case items themselves should be seen as they are all in one scope.

This isn't entirely true, since if you put { .. } around the statements of a single case clause, that _is_ a nested block-scope.

I understand it is scope when you put {...} around the statements. But the whole list of cases acts as if they are in one scope inside the switch when the case bodies are one-liner expressions as commented by @ericmathison

switch (true) {
   case true:
      let foo = 42;
    case 'anyvalue':
      console.log(foo);
}

Outputs 42

But when we put them in curly braces, scoping rules is effected:

switch (true) {
   case true:
      { let foo = 42; }
    case 'anyvalue':
      { console.log(foo); }
}

Throws an error
Uncaught ReferenceError: foo is not defined

Was this page helpful?
0 / 5 - 0 ratings