经典for循环的闭包问题

<script>
function fn() {
var arr = [];
for (var i = 0; i < 5; i++) {
arr[i] = function () {
return i;
};
}
for (var j = 0; j < arr.length; j++) {
console.log(arr[j]());
}
}
fn();
</script>

这道面试题非常的经典,为了方便查看和理解,我用一个数组来表示。

上面是一道经典的for循环的闭包面试题。不出所料地话就会打印5次5,我们来看浏览器

每天一个知识点——经典for循环的闭包问题_数组

我们解析一下为什么会这样呢?

因为 var 声明的变量是全局性的变量,所以每一次循环都会将 i 改变,而因为循环的缘故,下面函数有没有 i 这个变量,会向上查找,查找到的是已经循环完成的 i ,也就是5,所以一共打印了5次 i

然后我们想取得数组0到4,应该怎么做呢?

方法一

<script>
function fn() {
var arr = [];
for (let i = 0; i < 5; i++) { //改动在这里,var改为let
arr[i] = function () {
return i;
};
}
for (var j = 0; j < arr.length; j++) {
console.log(arr[j]());
}
}
fn();
</script>

我们解析一下为什么这样只是将 var 改成 let 就可以呢?

因为let是局部变量,它每一次循环都会是新的 i ,循环完都会销毁,所以能依次打印0到4。

方法二:

<script>
function fn() {
var arr = [];
for (var i = 0; i < 5; i++) {
arr[i] = (function (num) {
return num;
})(i)
}
return arr

}
console.log(fn()); //[0, 1, 2, 3, 4]
</script>

这里我采用的是匿名函数的方法,将变量 i 作为参数传入给内部函数,并自执行 i 次,然后再在内部return 这个函数,这样就可以得到我们想要的数组了。不过这样是比较难理解的。

方法三:

<script>
function fn() {
for (var i = 0; i < 5; i++) {
(function (num) {
setTimeout(function () {
console.log(num++);
}, 0)
}(i))
}
}
fn();
</script>

这里我们改写成定时的模式,先给出一个匿名自执行函数执行 i 次,并传入将 i 传入用num接收,然后执行自增,这样设计的话更巧妙,能巧妙地利用块级作用域地方式解决闭包,看起来更加易懂

每天一个知识点——经典for循环的闭包问题_i++_02