switch

Let’s briefly explore the switch statement, a sort-of syntactic shorthand for an if..else if..else.. statement chain.

  1. switch (a) {
  2. case 2:
  3. // do something
  4. break;
  5. case 42:
  6. // do another thing
  7. break;
  8. default:
  9. // fallback to here
  10. }

As you can see, it evaluates a once, then matches the resulting value to each case expression (just simple value expressions here). If a match is found, execution will begin in that matched case, and will either go until a break is encountered or until the end of the switch block is found.

That much may not surprise you, but there are several quirks about switch you may not have noticed before.

First, the matching that occurs between the a expression and each case expression is identical to the === algorithm (see Chapter 4). Often times switches are used with absolute values in case statements, as shown above, so strict matching is appropriate.

However, you may wish to allow coercive equality (aka ==, see Chapter 4), and to do so you’ll need to sort of “hack” the switch statement a bit:

  1. var a = "42";
  2. switch (true) {
  3. case a == 10:
  4. console.log( "10 or '10'" );
  5. break;
  6. case a == 42:
  7. console.log( "42 or '42'" );
  8. break;
  9. default:
  10. // never gets here
  11. }
  12. // 42 or '42'

This works because the case clause can have any expression (not just simple values), which means it will strictly match that expression’s result to the test expression (true). Since a == 42 results in true here, the match is made.

Despite ==, the switch matching itself is still strict, between true and true here. If the case expression resulted in something that was truthy but not strictly true (see Chapter 4), it wouldn’t work. This can bite you if you’re for instance using a “logical operator” like || or && in your expression:

  1. var a = "hello world";
  2. var b = 10;
  3. switch (true) {
  4. case (a || b == 10):
  5. // never gets here
  6. break;
  7. default:
  8. console.log( "Oops" );
  9. }
  10. // Oops

Since the result of (a || b == 10) is "hello world" and not true, the strict match fails. In this case, the fix is to force the expression explicitly to be a true or false, such as case !!(a || b == 10): (see Chapter 4).

Lastly, the default clause is optional, and it doesn’t necessarily have to come at the end (although that’s the strong convention). Even in the default clause, the same rules apply about encountering a break or not:

  1. var a = 10;
  2. switch (a) {
  3. case 1:
  4. case 2:
  5. // never gets here
  6. default:
  7. console.log( "default" );
  8. case 3:
  9. console.log( "3" );
  10. break;
  11. case 4:
  12. console.log( "4" );
  13. }
  14. // default
  15. // 3

Note: As discussed previously about labeled breaks, the break inside a case clause can also be labeled.

The way this snippet processes is that it passes through all the case clause matching first, finds no match, then goes back up to the default clause and starts executing. Since there’s no break there, it continues executing in the already skipped over case 3 block, before stopping once it hits that break.

While this sort of round-about logic is clearly possible in JavaScript, there’s almost no chance that it’s going to make for reasonable or understandable code. Be very skeptical if you find yourself wanting to create such circular logic flow, and if you really do, make sure you include plenty of code comments to explain what you’re up to!