admin管理员组

文章数量:1336143

My JavaScript book, "JavaScript The Definitive Guide, 6th Ed.", page 270 includes this text and code:

"... in a for loop, the initializer expression is evaluated outside the scope of the new variable"

let x = 1;
for (let x = x + 1; x < 5; x++) {
    console.log(x); // prints 2, 3, 4
}

When I run the above code (in the latest version of Chrome and FF) however, I get console errors:

ReferenceError: x is not defined

can't access lexical declaration `x' before initialization

Is the code in the book incorrect? (There's nothing on the book's errata site re: this.)

My JavaScript book, "JavaScript The Definitive Guide, 6th Ed.", page 270 includes this text and code:

"... in a for loop, the initializer expression is evaluated outside the scope of the new variable"

let x = 1;
for (let x = x + 1; x < 5; x++) {
    console.log(x); // prints 2, 3, 4
}

When I run the above code (in the latest version of Chrome and FF) however, I get console errors:

ReferenceError: x is not defined

can't access lexical declaration `x' before initialization

Is the code in the book incorrect? (There's nothing on the book's errata site re: this.)

Share Improve this question edited Jun 20, 2020 at 9:12 CommunityBot 11 silver badge asked Feb 12, 2019 at 15:26 Gerald LeRoyGerald LeRoy 1,2772 gold badges11 silver badges18 bronze badges 5
  • 3 x is being redeclared, that's the issue. – briosheje Commented Feb 12, 2019 at 15:27
  • 2 @briosheje no, it doesn't get redeclared. It gets shadowed. – Jonas Wilms Commented Feb 12, 2019 at 15:56
  • @JonasWilms true, indeed. Though the issue is still there. It's shadowed by the local x variable, right? – briosheje Commented Feb 12, 2019 at 15:57
  • @briosheje the issue is trying to access a variable inside of its initializer. The quoted paragraph is just wrong. – Jonas Wilms Commented Feb 12, 2019 at 16:07
  • Thanks everyone! This is making more sense to me now. I now have a MUCH better understanding of how the keyword let behaves! – Gerald LeRoy Commented Feb 12, 2019 at 20:08
Add a ment  | 

5 Answers 5

Reset to default 5

The problem is not really that x gets declared twice. It is just that you are trying to access the inner x before it got initialized:

 let x = x /* doesn't exist yet*/;
 

Wether there is another x in the outer scope (initializers in a for loop are inside their own scope) doesn't matter, the x will refer to the variable in the current scope, as it already got declared (due to hoisting), but wasn't initialized yet:

 let x = 0; // irrelevant

 { // x gets declared as part of this scope
   x; // thats an error too as x is not initialized yet
   let x = 1; // initialization
   x; // now it can be accessed
 }

The part between the beginning of a scope and a let declaration is called the "temporal dead zone" ...

"... in a for loop, the initializer expression is evaluated outside the scope of the new variable"

No, otherwise you couldn't refer to other variables in the initializer:

 for(let a = 1, b = a; ; )

As always, the definite answer can be found in the spec:

13.7.4.7 Runtime Semantics: LabelledEvaluation

IterationStatement : for ( LexicalDeclaration Expression ; Expression ) Statement

  1. Let oldEnv be the running execution context's LexicalEnvironment.

  2. Let loopEnv be NewDeclarativeEnvironment(oldEnv).

[...]

  1. Let boundNames be the BoundNames of LexicalDeclaration.

  2. For each element dn of boundNames [..]

    Perform ! loopEnvRec.CreateImmutableBinding(dn, true).

  3. Set the running execution context's LexicalEnvironment to loopEnv.

  4. Let forDcl be the result of evaluating LexicalDeclaration.

[...]

As you can see, the running execution context is loopEnv while the LexicalDeclaration (the initializers) gets evaluated, not oldEnv.

TLDR: Not only the example is wrong, but also the paragraph.

The only issue is that x is being redeclared shadowed (as mentioned by Jonas above), hence it throws an error.

Just remove the second let and everything will work as expected.

let x = 1;
for (x = x + 1; x < 5; x++) {
   //^---- note the missing "let" here. 
   console.log(x); // prints 2, 3, 4
}

If you copied that from a book, then that's a book issue.

https://jsfiddle/hto9udmj/

Further infos about variable declarations can be found here: https://developer.mozilla/en-US/docs/Web/JavaScript/Reference/Statements/let

Further infos about variables shadowing: An example of variable shadowing in javascript

Is the code in the book incorrect? (There's nothing on the book's errata site re: this.)

I believe the books was correct; when let was introduced for the first time years ago in Firefox.

Specifically, it didn't have the temporal dead zone, and it behaves internally more like var, just block scoped.

In Firefox 44, there was a breaking change that makes let and const following the standards:

https://blog.mozilla/addons/2015/10/14/breaking-changes-let-const-firefox-nightly-44/

Including the introduction of the temporal dead zone.

So, yes, the book now is incorrect; since you're trying to do something like:

let x = 0;
{
  let y = x; // `let` is block-scope,
             // so this `x` is actually the `x` 
             // defined below, not the one outside
             // the scope, hence the `ReferenceError`.
  let x = 1;
}

You are initializing x twice and thus getting error. Rename one x to i

let x = 1;
for (let i = x + 1; i < 5; i++) {
    console.log(i); // prints 2, 3, 4
}

The problem is that you redeclare x within the for loop and since let only exists within the given context, after the loop is done x doesn't exist anymore.

Either declare x once outside of the for loop, or use var. var adds the variable to a global scope, so it'll exist after the for loop is done.

let x = 1;
for (x = x + 1; x < 5; x++) {}
console.log(x);

With var:

for (var x = 2; x < 5; x++) {}
console.log(x);

本文标签: JavaScript for loop with letStack Overflow