【问题标题】:Double let declaration: How does let hoisting work in for loops?Double let 声明:让提升如何在 for 循环中工作?
【发布时间】:2023-04-07 19:36:01
【问题描述】:

鉴于以下简化代码,为什么会出现 ReferenceError?

    function foo(n){
        for (let i = 0; i < n; i++) {       
            console.log(i);
            let i = 10;
        }
    }

    foo(4);

// OUTPUT: ReferenceError: i is not defined

let 变量应该被提升为所有其他类型的声明。因此,在此代码中,i 被声明了两次。首先,在初始化关闭中,其次在 for 循环本身中。

为什么输出的是i is not defined而不是Identifier 'i' has already been declared

JS如何读取上面的代码?

【问题讨论】:

  • 这是一个很好的问题。如果我们删除了let i = 10,它将起作用。据我了解,let 不要为循环中的每个scope 使用hoist,所以我希望改为打印0,1,2,3

标签: javascript for-loop hoisting


【解决方案1】:

let 变量应该被提升为所有其他类型的声明。

let 变量没有提升,这与var 变量以及函数和类声明不同。 const变量是一样的。

在你到达声明它的行之前,该变量不会存在。

但是,在到达声明之前,您无法访问相关范围内的变量。这有点无益,被称为时间死区。 (Useful 2ality blog)

这很有帮助,因为在声明之前访问变量几乎总是错误的。或者,至少,它会令人困惑,并且会在您重构时导致错误。

function foo(n){
    for (let i = 0; i < n; i++) {       
        // i cannot be accessed
        let i = 10;
        // i is set to 10
    }
}

foo(4);

至于为什么不是“标识符已声明”错误,for 循环头中声明的变量的范围是与块中声明的变量分开的变量。这段代码中基本上有三个作用域:外层作用域,从for 循环头开始的作用域,以及从{} 包围的块开始的作用域。

【讨论】:

  • 来自文章: > 这里的原理如下: foo 不是未声明的,它是未初始化的。你应该知道它的存在,但不是。因此,被警告似乎是可取的。 const 和 let 像 var 和函数一样被“提升”。与其他人不同,它们没有被初始化。因此,在到达初始化(而不是声明)之前,您无法访问相关范围内的变量。
【解决方案2】:

下面发生的是 JavaScript 在同一个 for 循环中看到两个不同级别的块范围。

第一个在循环的初始化子句中声明 (let i = 0;),第二个在循环本身内。

这是您第一次运行代码时发生的情况:

  • 初始化子句中的leti = 0;
  • let inside for 循环:i is set to undefined

当 for 循环命中 console.log(i) 时,它会在循环内找到 let i 的声明,该声明覆盖先前声明和分配的 let i(在初始化子句中。

由于每次提升,letconst 已声明但未初始化(如 undefined),它会引发 ReferenceError。

【讨论】:

  • 就我而言,这是一个错误——至少是一个浏览器错误。我今天浪费了一个小时,因为没有警告我两次使用相同的变量名。必须就此向开发者发出警告。
【解决方案3】:

根据MDN:

function test(){
  var foo = 33;
  if (true) {
   let foo = (foo + 55); // ReferenceError
  }
}
test();

由于词法作用域,表达式 (foo + 55) 中的标识符“foo”计算为 if 块的 foo,而不是值为 33 的上层变量 foo。 在那一行中,if 块的“foo”已经在词法环境中创建,但还没有达到(并终止)它的初始化(这是语句本身的一部分):它仍然在时间死区。

这意味着如果循环变量在您的示例中为“读取”,(在您的情况下为 console.log,您不能将其分配给同名 var,因为它在“死区”中

MDN 参考:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let(在时间死区下)

【讨论】:

  • 不。你可以简单地删除let i = 10,你会看到它打印出0,1,2,3
  • 日志是在每个循环中创建的,而不是在循环结束后创建的。您可以通过在调试器中单步执行代码来确认这一点。
  • [...]Now in next loop iteration, now i is defined.[...] 这不是真的,i 将始终在评估 let i 时定义,因此对于循环的每次迭代,循环块中的 i 将在到达let i 之前不可用。
  • 一个更好的例子可能是developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… 下的第二个例子,因为for 初始化器中i 的词法范围在循环范围内的let i = 10; 范围内基本上被丢弃了.
  • 但这仍然不能真正解释for (let i = 0; i &lt; n; i++) {let i 属于哪个范围。它既不是foo 的范围,也不是循环体。
猜你喜欢
  • 1970-01-01
  • 2015-10-25
  • 1970-01-01
  • 2018-05-26
  • 1970-01-01
  • 2015-10-24
  • 2012-09-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多