Actually, it can't at all.
TL;DR: In your code, the variable bar
is declared and defined, but not initialized.
The first error is right: bar
was declared twice.
Also note, that it's a compile-time SyntaxError
, so it happens before the code gets evaluated, so it isn't affected by the thrown exception inside the variable declaration:
//SyntaxError
console.log('Evaluating code') //Never runs
let foo = 'bar'
let foo = 'baz'
But the second error isn't so obvious: why isn't bar
just undefined
?
After a lot of searching in the ECMAScript 6 specification, I've found the source of the problem. That's a "bug" (or at least a situation that hasn't taken care of) in the spec itself, but fortunately, it's very rare outside of a JS console.
You may know, that let
and const
variables have a so-called temporal dead zone, that throws ReferenceError
s when you try to look up or assign to variables before they're declared:
/* Just to make console fill the available space */
.as-console-wrapper{max-height:100% !important;}
<!-- Using separate scripts to show all errors -->
<script>
console.log(foo) //ReferenceError
const foo = 'bar'
</script>
<script>
bar = 'baz' //ReferenceError
let bar
</script>
That's because these variables' bindings are created before the containing code block get executed, but aren't initialized until the variable declaration statement evaluated. Trying to retrieve or modify the value of an uninitialized binding always results in a ReferenceError
.
Now, let's inspect the evaluation of the let
(and const
) statement's LexicalBinding (variable = value
pair), it's defined as follows:
LexicalBinding : BindingIdentifier Initializer
- Let bindingId be StringValue of BindingIdentifier.
- Let lhs be ResolveBinding(bindingId).
- Let rhs be the result of evaluating Initializer.
- Let value be GetValue(rhs).
- ReturnIfAbrupt(value).
- If IsAnonymousFunctionDefinition(Initializer) is true, then
- Let hasNameProperty be HasOwnProperty(value, "name").
- ReturnIfAbrupt(hasNameProperty).
- If hasNameProperty is false, perform SetFunctionName(value, bindingId).
- Return InitializeReferencedBinding(lhs, value).
Emphasis mine.
Without really going into detail, I'd like to highlight the most important things:
BindingIdentifier
is the variable name
Initializer
is the value to assign to it
ReturnIfAbrupt()
is an abstract algorithm, that returns from its caller with its argument if its argument is a Completion Record that represents an abrupt completion (e.g. a thrown exception)
InitializeReferencedBinding()
initializes the given Binding
The problem appears when an exception is thrown during the evaluation of the Initializer
.
When that happens, this:
GetValue(rhs)
...will return an abrupt completion, so the following line:
ReturnIfAbrupt(value)
...returns from the let
(or const
) statement with the abrupt completion record (i.e. re-throws the exception), so that line:
InitializeReferencedBinding(lhs, value)
...won't run at all, therefore, the variable's binding remains uninitialized and continues to throw ReferenceError
s when you try to look it up or assign to it.
These errors' message (foo is not defined
) is even more confusing and inappropriate, but that depends on the implementation, so I can't reason about it; however, probably it's because of another unhandled case.