What's the point of a scope?

const funcy = () => {
var a = "hello world"
let b = "hello everyone" 
 
  {
  //this is a scope
 let b = "shadow"
  }

console.log(b)
//returns hello everyone
}
funcy()

This is a well-known example of let vs var. But what are the practical applications of scopes?

Block scopes not associated with a function or control flow statement, huh? Seems counterintuitive that making a variable not accessible would help in some way, doesn’t it? But from time to time a good reason to do this will come up. Consider this code:

let request;
{
  const input = getInput();
  const helper = new Helper();
  request = helper.fixUp(input);
  helper.retire();
}
// do stuff with request

Suppose there are good reasons to prevent later code from accidentally using input and helper. Let’s say input is in some weird raw form with subtle differences that the codebase (other than fixUp) can’t handle correctly. And that retire puts helper into some state where it can’t be used anymore.

In this case, it’s better to have these go out of scope right away and to put most of the code outside that block where it can’t accidentally use them. If the code were to try to reference input or helper, you’d get a reference error right away instead of having to run to some point where little differences between input and request cause something to happen or for a later use of helper to give wrong results.

There are other ways to address this kind of problem. One thing is to name things more descriptively, e.g. inputRawDontUse. Another appealing option is to move that block into a function, which is especially nice if it’s self contained and otherwise makes sense as a single, more abstract step.

Other languages have features like this too. Here are some additional thoughts specific to JavaScript. JavaScript is garbage collected, so when your program is done with an object like helper above, the language runtime cleans it up for you, but not at any specific in the code. Other languages make stronger promises that an object like helper will be ‘destroyed’ exactly at when control leaves the block, and you can customize the ‘destructor’ to run some custom piece of code. It’s less error prone to write this (pseudocode):

class ManagedHelper {
  destructor() { this.retire(); }
}

...

{
  const a = new ManagedHelper();
  // use a
}

than to need explicit cleanup everywhere it’s used:

class Helper {
  retire() { ... }
}

...

const a = new Helper();
try {
  // use a
} finally {
  a.retire();
}

Also, JavaScript won’t allow you to assign a const separate from declaring it, so you coudn’t write this:

const result;
{
  // bunch of stuff leading up
  result = computation.getAnswer();
}

You’d have to declare it as let result;, which would give the false impression that you’re welcoming reassignment even if you aren’t. Some languages let you do this, and using these blocks is much nicer.

4 Likes

Wow, thanks for the detailed response! I’ve met tons of smart people but you’re by far the smartest.

Probably not the best thing to say around here, @CarlyRaeJepsenStan… heheh