getify / You-Dont-Know-JS

A book series on JavaScript. @YDKJS on twitter.
Other
178.28k stars 33.42k forks source link

Scopes & Closures Ch. 6 - Switch Statement Scoping #1670

Open ericmathison opened 4 years ago

ericmathison commented 4 years ago

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!

getify commented 4 years ago

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);
}
ericmathison commented 4 years ago

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:)

getify commented 4 years ago

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

getify commented 4 years ago

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.

ericmathison commented 4 years ago

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!

ghost commented 3 years ago

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

getify commented 3 years ago

@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.

danqulogy commented 3 years ago

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.

getify commented 3 years ago

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.

ghost commented 3 years ago

@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

danqulogy commented 3 years ago

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