for循环给数组元素赋值为函数,它们的执行结果都一样

291 阅读1分钟

关键词:作用域,闭包

这个问题与for循环+setTimeout遇到的问题实际上是一样的。

问题

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
console.log(a[0](), a[4](), a[9]()); // 10, 10, 10 (*)

从(*)结果可以看出,数组a的每个元素执行后都打印10。

原因

for循环结束后,数组a每个元素都成为了匿名函数,如下所示。同时var声明的全局变量i的值也变为了10。

console.log(a);
/*
[
  function () {
    console.log(i);
  },
  function () {
    console.log(i);
  },
  ...
]
*/

console.log(i); // 10

注意:别被这个函数中的标识符i迷惑了,它就是后面匿名函数需要用到的变量名。表示如果这个函数调用时,其所在的作用域中含i这个变量,就会打印i的值。

a[i] = function () {
  console.log(i);
};

在(*)处调用a[n]()相当于执行了:

(function() {
  console.log(i);
})();

因为这个函数的作用域链上只有全局作用域有变量i,即var声明的全局变量i(最终值为10),这就导致数组中每个元素执行结果都打印i的最终值10。

解决

// 方式1、let/const
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
console.log(a[0](), a[4](), a[9]()); // 0, 4, 9 (*)

// 方式2、闭包
for (var i = 0; i < 10; i++) {
  a[i] = (function foo() {
    return function bar() {
      console.log(i);
    }; // 返回的这个函数bar引用着函数foo内部作用域,并形成一个闭包
  })(i);
}
console.log(a[0](), a[4](), a[9]()); // 0, 4, 9 (*)

以上内容含自己的理解,仅用于自学记录。