Why do catch clauses have their own lexical environment?
Asked Answered
P

1

6

Consider the following excerpt from ECMA-262 v5.1 (which I recently saw in this question):

A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code. A Lexical Environment consists of an Environment Record and a possibly null reference to an outer Lexical Environment. 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.

I thought that meant the body of catch clauses would hoist its own variables like functions do, but apparently that's not the case:

var a = 1;
try {
    console.log(x); // ReferenceError
} catch(ex) {
    console.log(a); // 1, not undefined
    var a = 3;
}

Does anybody know why? Also, why does a catch clause need its own lexical environment?

Persecute answered 22/2, 2013 at 23:19 Comment(2)
"possibly null reference to an outer Lexical Environment." means it can also be not null, and when not null, it'll find global variables just fine.Cyril
@Mike'Pomax'Kamermans I was actually expecting that catch could have its own local variables, shadowing the globals. But it can't, because the Lexical Environment created for it is of another type (see Bergi's answer, and the spec at 10.3).Persecute
Z
9

Yes, catch clauses indeed have their own Lexical Environments. Check out what happens when it is evaluated: It creates a new one (deriving from the current one) and binds the exception-identifier to it. When executing the catch block, the current Execution Context's LexicalEnvironment is switched to the new one, while the VariableEnvironment("whose environment record holds bindings created by VariableStatements and FunctionDeclarations") stays unchanged.

console.log(a); // undefined - declared from within the catch,
                // but in the current VariableEnvironment
a = 1;
console.log(typeof ex); // undefined - no binding
try {
    console.log(ex); // a ReferenceError in this LexicalEnvironment
} catch (ex) { // introducing the new LexicalEnvironment
    console.log(ex); // …and it works here!
    var a = 3; // variable declaration
}

Fun fact: If you try to declare a function inside a catch clause (though syntactically invalid in a block, "function declaration statements" are often accepted), its scope will become the current VariableEnvironment so it will not be able to access the exception:

try {throw "some"} catch(x) { function y(){console.log(x, typeof x);} y(); }
                    // throws a ReferenceError for x   ^

(Update: this is no longer true in ES6, where block-level function declarations are valid and close over the block scope)

Zibeline answered 22/2, 2013 at 23:38 Comment(9)
Now that you said it, it makes total sense that a separate record is needed for the exception object. When you say the VariableEnvironment stays the same, is that because the specification doesn't say a new one should be created, or is it explicitly stated that it should stay the same?Persecute
Yeah, it is not stated explicitly, but since the Lexical Environment is a distinct component of the Execution Context I don't see what would change the Variable Environment - and there is no new context established, only the current one is modified (and reset after the block execution).Zibeline
Thanks, it's clear now. I learnt that the Variable Environment never changes within the same execution context, so the effect I was expecting from catch is not possible. It happens in functions because they create brand new execution contexts.Persecute
If the spec. doesn't explicitly say something changes, that is equivalent to it saying that the thing stays the same. In general, we don't bother to be explicit about "staying the same" because that is the default.Samos
@Zibeline Maybe i missed something but from this answer, I still don't understand WHY catch needs to create new lexical environment? Why not just bind the exception identifier to outer scope (variable environment) thus keeping both variable and lexical environments still the same? Also, now in ES6 does each block create a new lexical environment different from variable environment?Decomposition
@Decomposition Ah, that would've been another possibility I guess, iirc Python does that. But while I don't know about the designer's choices, it seems reasonable to me: an identifier should be available only in the block where it has a value. And yes, in ES6 each block creates a new lexical environment anyway.Zibeline
@Zibeline recently I've decided look at catch clause and I noticed that in the first place will be created Declarative Environment by catch clause, and in the second time Declarative Environment will be created when in catch clause we will evaluate Block statement. Hence there is the question, why do we create in catch clause Declarative Environment twice?Indication
@Indication Does it? What version of the spec are you reading? I think this would make for a good separate question. But probably the answer is that it's easier to specify this way, and makes no observable difference.Zibeline
@Zibeline of course draft :) But it doesn't matter, I checked old version of ECMA and saw same situation. I think same, it's easiest way store catch arguments in independent Environment Record from outer. And I don't imagine how to combine catch clause which also store block clause. P.S tc39.es/ecma262/#sec-runtime-semantics-catchclauseevaluation Look at 2 and 7 stepsIndication

© 2022 - 2024 — McMap. All rights reserved.