33

The ECMAScript 5 spec states the following:

Usually a Lexical Environment is associated with some specific syntactic structure of ECMAScript code such as a FunctionDeclaration, a WithStatement, or a Catch clause of a TryStatement and a new Lexical Environment is created each time such code is evaluated.

If my understanding is correct, then when a new Lexical Environment is created in JavaScript, a new scope is entered, which is why variables declared inside a function are not visible outside of that function:

function example() {
    var x = 10;
    console.log(x); //10
}
console.log(x); //ReferenceError

So in the above function declaration, a new Lexical Environment is created, which means x is not available in any outer Lexical Environments that may exist.

So the part of the quote above about Function Declarations seems to make sense. However, it also states that a new Lexical Environment is created for the Catch clause of a Try Statement:

try {
    console.log(y); //ReferenceError so we enter catch
} 
catch(e) {
    var x = 10;
    console.log(x); //10
}
console.log(x); //10 - but why is x in scope?

So how does the scope of a catch block work? Do I have a fundamental misunderstanding of what a Lexical Environment is?

James Allardice
  • 164,175
  • 21
  • 332
  • 312
  • This SO post is somehow related: http://stackoverflow.com/questions/6100230/javascript-catch-parameter-already-defined – Juri Oct 28 '11 at 08:16
  • Related: [Why do catch clauses have their own lexical environment?](https://stackoverflow.com/q/15034864/1048572) – Bergi Mar 17 '21 at 00:23

4 Answers4

31

If I understand it right, then what it probably means is that, in your code,

try {
    console.log(y); //ReferenceError so we enter catch
} 
catch(e) {
    var x = 10;
    console.log(x); //10
}

e will only exist in the catch block. Try console.log(e); outside the catch block and it will throw ReferenceError.

As with WithStatement, with ({x: 1, y: 2}) { }, x and y will only exist inside the with block.

But it doesn't mean that var declarations will be bound to the closest lexical environment. Actually, var declarations will be bound to the environment when entering an execution context.

10.5 Declaration Binding Instantiation: when the execution context in being entered, it will look for function declarations, arguments, and variable declarations and then create bindings in the execution context's VariableEnvironment.

So any variables that are declared using var will be accessible anywhere in the function regardless of the control structures or where it is defined inside the function. Note that this does not include nested functions as they're a separate execution context.

That means the var declarations will be bound to the closest execution context.

var x = 1;
(function() {
    x = 5; console.log(x); // 5
    if (false) { var x; }
    x = 9; console.log(x); // 9
})();
console.log(x); // 1

So in above code, x = 5; will set the x variable inside the inner function, because var x; inside if (false) { var x; } is bound to that function already before the function code is executed.

Thai
  • 10,746
  • 2
  • 45
  • 57
  • 1
    Ahh, this makes a lot of sense. The "So any variables that are declared using `var`..." paragraph sort of made it click. Thanks :) – James Allardice Oct 28 '11 at 08:26
  • Great answer and well explained – contactmatt Feb 22 '13 at 22:21
  • 4
    Note that even if you declare `e` at the top of the function containing the try-catch, the `e` in the `catch` block will not be the same variable. Like function parameters, it is a new variable local to the lexical context. – David Harkness Oct 03 '13 at 22:02
  • Netbeans (at least version 8.0.2) erroneously warns that the the error identifier in the catch clause is an undeclared global variable within the catch block. – Glenn Lawrence May 29 '15 at 00:39
10

From dev.opera (emphasis added)

The try-catch-finally construct is fairly unique. Unlike other constructs, it creates a new variable in the current scope at runtime. This happens each time the catch clause is executed, where the caught exception object is assigned to a variable. This variable does not exist inside other parts of the script even inside the same scope. It is created at the start of the catch clause, then destroyed at the end of it.

So it looks like the only thing that's really within the scope of the catch is the exception itself. Other actions seem to be (or stay) bound to the outer scope of the catch (so in the example the global scope).

try {
    console.log(y); //ReferenceError so we enter catch
}
catch(e) {
    var x = 10;
    console.log(x); //10
}
console.log(e.message); //=> reference error

In ES5 these lines may be relevant (bold/emphasis added) to this:

  1. Let oldEnv be the running execution context’s LexicalEnvironment.
  2. Let catchEnv be the result of calling NewDeclarativeEnvironment passing oldEnv as the argument.

Also at the end of that part it states:

NOTE: No matter how control leaves the Block the LexicalEnvironment is always restored to its former state

KooiInc
  • 119,216
  • 31
  • 141
  • 177
  • Yeah I noticed that the error object was only in scope inside the `catch`. Still doesn't make sense though. I can only assume that either I still have a complete misunderstanding or (probably not surprisingly) the spec has not been followed properly. Reading section 12.14 (http://es5.github.com/#x12.14), it seems to further say that any variable declared inside the `catch` will not be in scope outside of it – James Allardice Oct 28 '11 at 08:06
  • I've added 2 lines from that ES5 section entitled "The production Catch ...". As far as I understand from those lines only the error is scoped locally using `catchEnv` (6-8). Other stuff gets back to `oldEnv` at the end. – KooiInc Oct 28 '11 at 08:18
7

Since it was standardized in ES3, a catch () {} clause is (to my knowledge) the only pre-ES2015 javascript construct that creates a block-level scope, simply because it is the only block-level construct that comes with an argument.

It is the reason why it's been used as a polyfill by next-generation javascript transpilers to compile this:

ES2015:

{ let priv = 1; }
console.log(priv); // throws ReferenceError

to this:

ES5 and lower:

try { throw void 0 } catch (priv) { priv = 1 }
console.log(priv); // throws ReferenceError
Christophe Marois
  • 6,471
  • 1
  • 30
  • 32
0

I was puzzling around with this question as it seemed I had already come across a similar problem, not related to the catch block in specific but within a function definition. Now I just remembered and I actually also blogged about that problem a couple of weeks ago :).

Take this code:

function test(){
  var x = "Hi";

  (function(){
    console.log(x);
    var x = "Hi, there!";
  })();
}

Code: http://jsbin.com/alimuv/2/edit#javascript,live

What's the output?? By quickly looking at it, one might assume it to be "Hi", as the scope of the variable x is taken from the function's outer scope. Instead, undefined is printed out. The reason is the same as for the catch block issue: lexical scoping, meaning that the scoping is created at function definition and not at run-time.

Juri
  • 32,424
  • 20
  • 102
  • 136