Reputation: 7821
Within the ECMAScript specification, where can we find a clear specification on why let
and const
are not accessible outside of Lexical Environments created with BlockStatements (as opposed to variables declared with var
)?
If BlockStatements now create new lexical environments, then let
and const
declarations should not create variables accessible outside that lexical environment, but var
variables should. I am trying to understand where exactly that behaviour is specified in the latest ECMAScript specification.
From 13.3.1 Let and Const Declarations:
let and const declarations define variables that are scoped to the running execution context's LexicalEnvironment. The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable's LexicalBinding is evaluated.
From 13.3.2 Variable Statement:
A var statement declares variables that are scoped to the running execution context's VariableEnvironment. Var variables are created when their containing Lexical Environment is instantiated and are initialized to undefined when created.
As seen, both variable declarations create variables when their containing Lexical Environment is instantiated. Which, in the case of a BlockStatement is when the compiler enters the block.
From 8.3 Execution Contexts:
LexicalEnvironment and VariableEnvironment components of an execution context are always Lexical Environments
Upvotes: 1
Views: 200
Reputation: 707396
As you saw in the description of var
, it is scoped to the running execution context's VariableEnvironment
. There is a top level VariableEnvironment
and then a new one is created when you enter a function and then in this part about Execution Contexts, it says this:
The LexicalEnvironment and VariableEnvironment components of an execution context are always Lexical Environments. When an execution context is created its LexicalEnvironment and VariableEnvironment components initially have the same value.
So, at the start of a function, the LexicalEnvironment and VariableEnvironment are one and the same.
Then, down in 13.2.13 Runtime Semantics: Evaluation Block: { }, you can see that a new LexicalEnvironment
is created when you enter the block and the prior one is restored when you leave the block. But, there is no mention of a new VariableEnvironment
when you enter or leave a block (because that stays constant within the function).
So, since let
and const
are scoped to the LexicalEnvironment in which they are declared and that is local to a block, it would not be accessible outside the block.
But, var
is scoped to the VariableEnvironment
which is only created and scoped to the whole function, not to a block.
let
and const
variables can't be accessed outside their LexicalEnvironment because their definitions are not in the scope hierarchy as soon as the execution context leaves their block (as soon as you leave the block, their LexicalEnvironment is essentially popped off the stack and is no longer in the scope search chain for the interpreter to find variables).
When the specification adds this:
The [
let
andconst
] variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable's LexicalBinding is evaluated.
This means that you are denied access to them even in their own Lexical Environment until their definition is evaluated. In layman's terms, this means they are not hoisted to the top of their scope like var
is and therefore cannot be used until after their definition. This is implemented by not initializing a let
or const
variable in the LexicalEnvironment
until its statement runs and the GetBindingValue()
operation which looks up a variable will see that it is not yet initialized and will throw a ReferenceError
. var
variables are initialized immediately to undefined
so they do not cause this ReferenceError
.
You can see how this works in this code:
let x = 3;
function test() {
x = 1;
let x = 2;
console.log("hello");
}
test();
At the let x = 3
line a variable x
is initialized in the outer LexicalEnvironment.
Then, when you call test()
, at the start of that function, a new LexicalEnvironment is created, the new declaration for x
in this block is placed into that new LexicalEnvironment
, but not yet initialized.
Then, you get to the x = 1
statement. The interpreter looks up x
, finds it in the current LexicalEnvironment
, but it is uninitialized, so it throws a ReferenceError
.
Per the question in your comment:
I've been turning the spec upside down, but have a hard time spotting that VariableEnvironments are only created for functions. Could you perhaps add an answer showing what steps in the spec you follow to reach said conclusion?
You have to just go through all the places in the spec where a VariableEnvironment
is created and you will find that the only places this happens is the beginning of a function execution and at the top level.
For example, here's one on those places: PrepareForOrdinaryCall. There are a few others.
But, no place does it ever describe this happening for the start of a block, only the start of a function.
The way these specifications are written, they describe when things do happen, not when they don't happen (which makes some logical sense), but it means to prove something doesn't happen, you have to fail to find anywhere that says it does happen.
Upvotes: 5