case 1
var a // ===> undefined
let a // ===> SyntaxError: Identifier 'a' has already been declared
case 2
a = 1 // ===> 1
var a // ===> undefined
let a // ===> undefined
Why case 2 does not throw an exception?
case 1
var a // ===> undefined
let a // ===> SyntaxError: Identifier 'a' has already been declared
case 2
a = 1 // ===> 1
var a // ===> undefined
let a // ===> undefined
Why case 2 does not throw an exception?
V8 developer here.
The first thing worth pointing out is that there are differences between regular script execution and "console mode" (a.k.a. "REPL mode"). The former is specified by the JavaScript standard, so all engines should behave the same there; otherwise it's a bug. The latter is not specified; it should obviously be similar to the former, but due to the interactive usage it turns out that it makes sense to have some minor differences.
Let's look at a few concrete examples and explain what's going on.
(1) Re-declaring a let
-variable is invalid in regular script. In the DevTools console, that turns out to be annoying though:
> let a = 42j // typo: I meant "42"
< Uncaught SyntaxError: Invalid or unexpected token
> let a = 42 // Let's try that again
< Uncaught SyntaxError: 'a' has already been declared
// Aaaargh!!!
So a while ago Chrome made some changes to special-case the behavior around let
-redeclarations for the console, which is why you can now see the following difference:
> let a; let a; // One script: redeclaration error
> let b
> let b // Separate evaluations: OK, no error.
If you executed the last two lines as part of regular script execution, you'd get the same error as when putting both declaration on the same line in DevTools.
(2) var
declarations vs. implicit global variables: there is a small difference between c = 1
and var d = 1
: the former creates a configurable property on the global object, the latter creates a non-configurable property, so while the two are almost equivalent, they can still be distinguished afterwards. Now, in regular script execution, var
declarations are hoisted, so if you have an explicit declaration following an implicit use of the same variable:
e = 1;
var e = 2;
then this gets transformed internally to: var e; e = 1; e = 2
. So in this case, you can't observe the configurability difference.
In console mode, the exact same behavior isn't possible: when you execute e = 1
, the engine can't know yet that you're going to type var e = 2
next. So it creates e
as a configurable property of the global object. When var e
follows, it doesn't do anything, because the e
property already exists.
(3) let
variables vs. global object properties: variables declared with let
do not create properties on the global object, they are in their own scope, shadowing the global property. Here's how you can observe that effect (both in regular script and in the console):
f = 1
let f = 2
console.log(window.f, f) // prints "1 2"
(4) Limitations to redeclarations in REPL mode: when REPL mode with its loosened restrictions on normally-invalid redeclarations was built for V8, the team decided to (continue to) disallow redeclarations that would change the kind (var
, let
, const
) of a variable, as well as redeclaration of consts:
> var g
> var g // OK
> let g // error: already declared
> const g = 1 // error: already declared
> let h
> let h // OK (REPL-mode special case)
> var h // error: already declared
> const h = 1 // error: already declared
> const i = 1
> const i = 2 // error: already declared
> var i // error: already declared
> let i // error: already declared
(So there's only one difference to regular script execution here, see the comment on that line above.)
Armed with this knowledge, we can now get back to your original question:
var a // Creates a variable of `var` kind.
let a // Redeclaration -> error
a = 1 // Creates global object property.
var a // Does nothing, because `a` already exists.
let a // Creates a variable of `let` kind, shadowing global property.
TL;DR: Use let
for everything, and your code will behave reasonably.
var
has weird semantics that can't be fixed because it would break backwards compatibility with existing websites. Forgetting the keyword entirely gives even weirder behavior, especially once functions are involved.
When you define a variable without var
, let
, or const
, it is entered into the global scope. Try this:
In the JS interpreter of your choice, do this:
>>> a = 5
>>> global
You'll see a bunch data, and at the very bottom, you'll see a = 5
.
Now, enter
>>> var a = 7
>>> global
You'll see the same thing, but now a is 7.
Restart your interpreter and enter
>>> a = 5
>>> let a = 7
>>> global
You'll see a
still equals 5! This gives us a hint that var
and let
aren't using the same scope. Restart your interpreter.
>>> a = 5
>>> let a = 6
>>> var a = 7 // Syntax Error!
Now, try this:
>>> var b = 6
>>> var b = 7
Notice how there's no syntax error? We've discovered that global variables will not throw syntax errors if you redeclare them at the global level. However, you cannot redeclare a variable at the global level that is already declared at a lower level. let
creates a variable in a scope below var
, which declares it in the global scope. That's why when you run var
twice, nothing happens, but when you run let
then var
(or let
then let
), you get a syntax error.
However, as we can see from the other answers, this is actually not true of all interpreters. My local version of Node has the same results you do, while some other interpreters will throw a Syntax Error for case 2.
Case 2 does not throw an exception because of the a = 1
.
Remember that let
variables are not initialized until their value is evaluated.
In comparison to var
, var
is initialized with undefined
unlike let
variables