最后的行(尚未执行)怎么可能影响代码的开头?为什么它会抛出不正确的错误?

How is it possible that lines in the end (which were not executed yet) affect the beginning of the code? And why does it throw an incorrect error?

所以我的代码中有一个 x is not defined 错误,这让我有点困惑,因为 x 之前已经定义了几行。我不得不花一些时间调整我的代码,删除和添加行,直到我设法理解它发生的原因。在我删除所有不必要的信息后,现在的代码如下所示:

let foo = 2;
console.log(foo);

if (foo === 2){
    console.log(foo);
    let foo = 1;
}

它在第 5 行抛出 foo is not defined。当我尝试 console.log(foo) 时弹出错误!如果我删除第 6 行 let foo = 1; 代码工作正常。我的意思是在我第二次声明 foo 之前发生了错误。所以第一个问题是:

  1. 第 6 行(尚未执行)怎么可能使第 5 行以错误结束?

我不明白的第二件事是为什么它说 foo is not defined 而不是 foo has been already declared。 如果我将第二个 let 替换为 var,第 6 行将出现一个错误,它会显示 foo has been already declared,所以它看起来不错。但是将 let 设置为第二个标识符总是会引发不正确的错误。

  1. 为什么会抛出不正确的错误?

在测试不同的场景后,我注意到结果取决于我使用的标识符:

identifiers |            result
----------------------------------------------
  var var   |      the code works well
  var let   |       not defined error
  let var   | has been already declared error
  let let   |       not defined error

所以第三题是:

  1. 为什么每个人都反对使用 var,而在这种情况下双重使用 var 是代码完美运行的唯一方法?是例外吗?
  1. How is it possible that line 6 (which hasn't been executed yet) makes line 5 end up with an error?

因为用 letconstclass 声明的绑定范围(松散地,"variables")是整个块,而不仅仅是它们所在的位置重新声明到块的末尾。从代码进入块到执行let语句之间的时间称为临时死区(TDZ),在此期间绑定存在但未初始化且无法使用以任何方式。即使在代码流中遇到 let foo 之前,块中的 let foo 也会遮挡外部 foo

除了范围之外,这个 TDZ 是 varlet 之间的最大区别在于 var 创建绑定 并且 初始化它到 undefined,无论 var 语句在范围内的哪个位置。相反,let(以及 constclass)创建绑定,但 初始化它直到稍后,当 let (const, class) 在逐步执行代码中遇到。您不能使用未初始化的绑定。

  1. Why does it throw an incorrect error?

没有错。你可以说它措辞不当。 :-) 基本上是说 "you can't use foo here, it's not initialized." 来自 V8 的当前错误消息(Chrome 中的 JavaScript 引擎、Chromium、Brave、新的基于 Chromium 的 Edge 和 Node.js)在我看来,更清晰:

Uncaught ReferenceError: Cannot access 'foo' before initialization

当您使用 let 声明变量时,它在当前代码块的范围内有效。您的第二个 let foo 声明定义了一个与第一个变量不同的 foo 并且它仅在 if 块中有效。但是,您在定义它之前使用它,因此您会正确地得到它尚未定义的错误。

如果您确实打算有两个不同的 foo 变量,我建议您将它们命名为其他名称(例如 foo1 和 foo2)以避免冲突。然后很明显您在定义变量之前就在使用它。

let foo1 = 2;
console.log(foo1);

if (foo1 === 2){
    console.log(foo1);
    let foo2 = 1;
}

如果您的意思是第 5 行使用 foo 的第一个实例设置为 2,那么您已经通过 if 代码块中发生的新定义隐藏了它。

如果您的意思是要在第 5 行使用设置为 1 的 foo,那么您应该将其定义移至使用前。

请注意,使用 var 会产生不同的结果,因为 var 变量的范围比 let 变量的范围更广。请参阅具有此定义的 here

let allows you to declare variables that are limited to a scope of a block statement, or expression on which it is used, unlike the var keyword, which defines a variable globally, or locally to an entire function regardless of block scope.

为了让它更清楚,我在代码的每个阶段用变量的状态标记了你的代码:

let foo = 2;          // foo defined and set to 2
console.log(foo);     // foo defined and set to 2

if (foo === 2)        // foo defined and set to 2
{                     // <-- start of the if-block!
    console.log(foo); // foo not defined yet
    let foo = 1;      // foo defined and set to 1
}                     // <-- end of if-block!
console.log(foo);     // foo defined and set to 2