Vedaant Arya
Vedaant Arya

Reputation: 549

Weird (let) variable assignment behavior in node repl - can't assign values

I'm using Node v12.14.1 on Windows 10 in cmd.
When assigning the value of an undefined function to a variable:

let a = f();

I get:

Thrown:
ReferenceError: f is not defined

Which is fine. But when I try:

a = 2;

I now get:

Thrown:
ReferenceError: a is not defined

And when I try:

let a = 2;

I get:

Thrown:
SyntaxError: Identifier 'a' has already been declared

So, a variable declared using let, when assigned the value of an undefined function, has its identifier already declared, and is not defined, at the same time.
Is this intended? Am I missing something here? Is this a bug?
The same does not happen when using var in the undefined function assignment, or when not using anything (global variable).

Upvotes: 11

Views: 514

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074495

REPLs are funny, but no, this behavior isn't a bug, it is indeed per spec. It's something you couldn't see in a non-REPL environment, though.

A let statement creates a binding¹ upon entry to the scope where let appears, but doesn't initialize it (unlike var, which initializes it with undefined). (In the case of the REPL "entering the scope" is basically just before it executes the code you've given it.) The initialization happens when the initialization part of the let statement happens, later when you you reach that statement in the step-by-step execution.² In your code, though, you never reach that part of the statement, because when the initializer was evaluated, it threw an error.

At that point, there's nothing you can do with a, because it exists but isn't initialized (it's stuck in the Temporal Dead Zone), and the only thing that could have initialized it (the original let a = f(); statement) failed and can't be run again.

The reason you can't see that in non-REPL code is that the error would take you out of the scope where a has been created but not initialized. Consider:

try {
    let a = f(); // ReferenceError
    // Execution in this block never continues
} catch {
    // Execution arrives here...but `a` is not in scope
}

Since the error exits the scope, you won't see this outside of a REPL. You could see the same error messages easily enough, by trying to use something before its let x or const x (or class X) is reached in the code execution, like this:

a = 1;

let a;

or this:

console.log(a);

let a;

...but you can't have that exact same situation.


¹ binding - an entry for the variable (and similar) in the execution context's environment record

² If the let statement doesn't have an initializer, undefined is used at this point.

Upvotes: 7

Related Questions