Magnus
Magnus

Reputation: 7821

ECMAScript: Where can we find specification about accessibility of let/const variables

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

Answers (1)

jfriend00
jfriend00

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 and const] 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

Related Questions