为什么不在函数副本内分配变量而不是引用它

Why doesn't assigning variables inside a function copy instead of referencing it

我有这个代码:

function makeArmy() {
  let shooters = [];

  let i = 0;
  while (i < 10) {
    let shooter = function () {
      console.log(i);
      // that should show its number
    };
    shooters.push(shooter); // and add it to the array
    i++;
  }

  // ...and return the array of shooters
  return shooters;
}

let army = makeArmy();

// all shooters show 10 instead of their numbers 0, 1, 2, 3...
army[0](); // 10 from the shooter number 0
army[1](); // 10 from the shooter number 1
army[2](); // 10 ...and so on.

发生这种情况是因为 i 始终是对 makeArmy() 函数内部外部 i 的引用。

如果我们调整代码为:

function makeArmy() {
  let shooters = [];

  let i = 0;
  while (i < 10) {
    // copy the varible i into the while lexical scope
    let j = i
    let shooter = function () {
      console.log(j);
      // that should show its number
    };
    shooters.push(shooter); // and add it to the array
    i++;
  }

  // ...and return the array of shooters
  return shooters;
}

let army = makeArmy();


army[0](); // 0
army[1](); // 1
army[2](); // 2

以上代码按预期工作,因为我们没有在 shooter() 中引用变量 i,而是将其复制到 j 中,从而使其可用于射手函数本身范围。

现在,我的问题是,为什么在函数本身内部复制 i 变量不起作用?即使我仍在有效地复制变量?我在这里错过了什么?

代码:

function makeArmy() {
  let shooters = [];

  let i = 0;
  while (i < 10) {
    let shooter = function () {
      let j = i;

      console.log(j);
      // that should show its number
    };
    shooters.push(shooter); // and add it to the array
    i++;
  }

  // ...and return the array of shooters
  return shooters;
}

let army = makeArmy();

// all shooters show 10 instead of their numbers 0, 1, 2, 3...
army[0](); // 10 from the shooter number 0
army[1](); // 10 from the shooter number 1
army[2](); // 10 ...and so on.

当您在射手函数中调用 console.log(i) 时,i 的值指的是 while 循环结束后的值,因此 i 将是它的最后一个值。在您的情况下,i 的值随时间变化,显示它的最后一个值。当您将其分配给变量 (let j = i) 时,您复制的是该值而不是引用,因此它会为您提供所需的结果。

因为您复制的时间 i 已经是 10

其实在inner函数里加上let j = i,跟直接用i没什么区别,因为那个赋值也是只有当您调用该内部函数,如前所述,此时 i 已经是 10,因为调用发生在循环完成很久之后,例如army[0]().

正如您所指出的,如果您将 let j = i 移到内部函数之外(但仍在循环内),它就会起作用。这样,对局部变量 j 的赋值会在每次迭代中立即发生,内部函数稍后会引用这 10 个不同的 j 及其先前设置的正确值i 具有您想要的相应值的时间点。


顺便说一下,如果你使用 for 循环,这个问题就不会存在,因为 forlet 有一些“魔法”可以创建 semi-separated 每次迭代的范围(确切的工作方式是 a bit more complicated)。

  for (let i = 0; i < 10; i++) {
    let shooter = function () {
      console.log(i);
    };
    shooters.push(shooter);
  }