简体   繁体   中英

The strange behavior of let and var in Javascript

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" (aka "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 var e; e = 1; e = 2 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM