JavaScript 中的动态与词法范围/循环混淆

Dynamic vs. lexical scoping in JavaScript / loop confusion

我遇到了一个微妙的错误,提炼如下:

function huh() {
    for (var i = 0; i < 10; i++) {
        if ( i % 2 == 1 ) {
            var x = i;
            console.log(i + ' ' + x);
        }
        else {
            console.log(i + ' ' + x);
        }
    }
}

huh();

首先,即使是经验丰富的 JavaScript 程序员,我也会挑战他们仅在纸上正确预测该程序的确切输出。但主要是,JavaScript 似乎混合了动态和词法范围。而且似乎没有块作用域,只有函数作用域,这基本上颠覆了我在 JavaScript 中的整个作用域概念。有人可以参考标准进行解释,也许还有一些理由吗?这似乎非常违反直觉。

从 ecmascript 版本 6 开始,JavaScript 中使用 let 关键字进行了块范围界定。

除此之外,它与 var 语句一起工作的方式是语句在函数执行之前浮动到函数的顶部。变量声明发生在其他任何事情之前,无论它们位于哪个块中,包括 for 循环语句内部。变量的实际赋值确实发生在适当的位置,因此它们是未定义的,但不是未声明的,直到它们被赋值的代码行。

至于你的具体例子,请记住像数字这样的原始值不是通过引用而是通过值分配的,并且 x 的更新只会发生在奇数上并且第一次是未定义的,但如果它是对象,一旦您将一个对象分配给另一个对象(无论 if/else 条件如何),它们将引用同一个对象。

为了进一步混淆,请注意浏览器中的控制台是异步的,因此您可以将对象打印到控制台(虽然不是原语)并在您无法获得对象状态的地方检查它们 console.log 语句,但在稍后的时间。这对于调试来说非常混乱。

是的JavaScript在实践中肯定有一点学习曲线。

这个有点令人费解的效果是函数作用域variable hoisting的组合。

你应该认为上面的代码等价于:

function huh() {
  var i, x;
  for (i = 0; i < 10; i++) {
    if ( i % 2 == 1 ) {
      x = i;
      console.log(i + ' ' + x);
    }
    else {
      console.log(i + ' ' + x); }
  }
}