JavaScript 中未初始化变量的范围

Scope of uninitialized variable in JavaScript

我不明白为什么这段代码的行为是这样的:

for (var i = 1; i < 3; i++) {
    var j;
    if (!j) {
        j = 1;
    } else {
        alert("why are we here? j shouldn't be defined but it's " + j);
    }
}

(jsFiddle)

如果我将 j 设置为 null 并检查 null,它会按照我认为应该的方式工作。

这不是 Java、C#、C++ 等的工作方式,因此会造成混淆。

这是因为 JavaScript 中的变量作用域为函数(如果不在函数内,则为全局作用域)。 var 不在循环内,花括号不定义新的闭包。基本上,j 的值在循环体之后仍然存在,而 var 不会将 j 重新定义为 undefined。这就是显式设置 var j = null; 具有预期效果的原因。

示例 1:

考虑这一点的一个好方法是,任何时候你用 var 声明一个变量,就像这样。

function someFunc() {
    for(var i = 0; i < 3; i++){
        var j;
    }
}

解释器像这样提升变量声明。

function someFunc() {
    var i;
    var j;
    for(i = 0; i < 3; i++){
    }
}

请注意,由于 var j 声明被提升到函数的顶部,该声明实际上在循环中什么也不做。

示例 2:

但是,如果您要像这样用 null 初始化变量。

function someFunc() {
    for(var i = 0; i < 3; i++){
        var j = null;
    }
}

会这样解读

function someFunc() {
    var i;
    var j;
    for(i = 0; i < 3; i++){
        j = null;
    }
}

注意每个循环如何将 j 设置为 null

ES6 let 关键字:

ES6 中有一个关键字会像这样在循环中创建作用域,它就是let关键字。请记住,目前浏览器对 let 关键字的支持很差。

for (var i = 1; i < 3; i++) {
    let j;
    if (!j) {
        j = 1;
    } else {
        alert("why are we here? j shouldn't be defined but it's "+ j);
    }
}

您的 for 循环第一次执行循环体时,j 未定义,因此您的代码集 j=1。在循环的后续迭代中,j 已经定义并设置为 1,因此它会按预期进入您的 else 子句。

这是因为在 Javascript 中用 var 定义的变量是函数范围的,而不是块范围的,如果不在函数内部,那么它们是全局的。因此,在您的 jsFiddle 中只有一个变量 j 并且 for 循环的每次迭代都使用相同的变量(因此继承了上一次迭代的值)。

如果您在 for 循环体内初始化 j = null; 它将起作用,因为这样您会为每次迭代重新初始化它,而不是使用上一次迭代的值。

ES6 建议添加 let 声明,该声明的作用域是最近的块。有关 let.

的更多信息,请参阅 What's the difference between using "let" and "var" to declare a variable?

JavaScript 中没有块范围,只有 global and function scopes. Although, JavaScript variable statements are so flexible that they will give the programmer the illusion that a block scope could exist, but the truth is that variables declared in JavaScript functions are later hoisted 解释器。这意味着它们的声明被移动到最近声明的函数的顶部。以此为例:

function test() {
   console.log('a test');
   for (var i = 0; i < 100; i++) {
      var k = i + Math.random();
      console.log(k)
   }
}

JavaScript 解释器在内部将 "transform" 代码变为以下内容:

function test() {
   var i, k; // var declarations are now here!

   console.log('a test');
   for (i = 0; i < 100; i++) {
      k = i + Math.random();
      console.log(k)
   }
}

它将所有 var 声明移动到最近函数声明的开头。在您的代码中,提升将创建以下代码:

// A anonymous function created by jsFiddle to run your script
function () {
   var i, j;

   for (i = 1; i < 3; i++) {
      if (!j) {
         j = 1;
      } else {
         alert("Why are we here? j shouldn't be defined, but it's " + j);
      }
   }
}

变量第一次是undefined,然后被赋值1,然后你的消息被打印出来。