3

I am working with try-catch blocks in JavaScript and have run into some variable scoping behaviour I don't completely understand.

I understand that console.log(boo) prints 20 to the console because the variable has been declared with the var keyword and hence it is functionally scoped (i.e. not block scoped to the catch block).

However, I don't understand why the err variable isn't also scoped to the IIFE in the same way as the boo variable. Therefore I don't understand why it is undefined outside of the catch block.

(function() {
  try {
    throw new Error();
  } catch (err) {
    var err = 10;
    var boo = 20;
    console.log(err); //'10' (as I expect)
  }
  // Why doesn’t this log '10' ???
  console.log(err); // 'undefined' (but I expected '10')

  console.log(boo); // '20' (as I expect)
})();
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
thedev19
  • 151
  • 1
  • 7
  • 2
    This is one of those little-known curiosities of JS - although (before ES6) variables always had function scope rather than block scope, the error variable in a `catch` block has always been block-scoped. – Robin Zigmond May 02 '19 at 08:36
  • you can do `var boo = err;` and then use `boo` outside. – Barmar May 02 '19 at 08:39
  • @barmar obviously `err` is still function scoped. This is not about scoping. – Jonas Wilms May 02 '19 at 08:42
  • Closely related, if not duplicate: [Why do catch clauses have their own lexical environment?](https://stackoverflow.com/q/15034864/1048572) – Bergi May 02 '19 at 08:42
  • Actually this should be a syntax error. Fromnthe spec `It is a Syntax Error if any element of the BoundNames of CatchParameter also occurs in the VarDeclaredNames of Block.` – Jonas Wilms May 02 '19 at 08:44

2 Answers2

7

I found the answer.

It is because the exception 'identifier' (which is the 'err' variable in the example above) is not available after the catch block has finished executing. This seems like a special case of handling scoping in JS and is definitely a 'gotcha' until one does some digging.

Hopefully this helps someone else!

From the docs: enter image description here

thedev19
  • 151
  • 1
  • 7
5

While var hoists the symbol to the top of the function block, the catch clause's "argument" is local to the clause. Since you have named the hoisted value the same as the clause symbol, this can be rather confusing. In essence, your code is the same as:

(function () {
var err;
var boo;
try {
    throw new Error();
  } catch (err) {
    err = 10; // Set the catch's "local" `err` identifier to `10`
    boo = 20; // Set the hoisted `boo` identifier to `20`
    console.log(err);
  }
  console.log(err); // The hoisted `err` was never set.
  console.log(boo); // ’20’ (as I expect)
})();

You might also think of the err in the catch-clause like a parameter to a function/clause; its scope is local to the function and has nothing to do with the global scope (whether or not the symbol exists in global scope).

bill.lee
  • 2,207
  • 1
  • 20
  • 26