James Allardice
James Allardice

Reputation: 166031

JavaScript catch clause scope

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?

Upvotes: 33

Views: 8508

Answers (4)

Christophe Marois
Christophe Marois

Reputation: 6729

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

Upvotes: 7

Juri
Juri

Reputation: 32920

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.

Upvotes: 0

KooiInc
KooiInc

Reputation: 122956

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

Upvotes: 10

Thai
Thai

Reputation: 11364

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.

Upvotes: 31

Related Questions