循环中的闭包,将可能导致一种奇怪的错误
function outside() { //* 外部函数
var arr = [];
for (var i = 0; i < 3; i++) {
arr[i] = function inside() { //* 内部函数
return i;
}
console.log("%c 💎: outside -> arr[i] ", "font-size:16px;background-color:#725f2f;color:white;", arr[i]()); //* 0、1、2
}
return arr;
}
var outerArr = outside();
console.log(outerArr[0]()); //* 3
console.log(outerArr[1]()); //* 3
console.log(outerArr[2]()); //* 3
解释说明:
- 内层函数,使用了外层函数的变量。且该变量i使用var声明,具有函数作用域,
2。 随着循环遍历,外层函数的变量i,会一直i++,直至循环结束,i最终等于3; - 随着循环遍历,同时创建了三个不同的函数,他们都指向外层函数的变量i,
- 由于循环时,内层函数没有立即执行,也就不会输出循环时增在变更的变量i(0、1、2);
- 当内层函数在外部执行时,内层函数中使用的外层函数的变量i是变量i 最新的值
解决方案1、for遍历时,使用let来声明,为循环变量i生成块级作用域
function outside() { //* 外部函数
var arr = [];
for (let i = 0; i < 3; i++) {
arr[i] = function inside() { //* 内部函数
return i;
}
console.log("%c 💎: outside -> arr[i] ", "font-size:16px;background-color:#725f2f;color:white;", arr[i]()); //* 0、1、2
}
return arr;
}
var outerArr = outside();
console.log(outerArr[0]()); //* 0
console.log(outerArr[1]()); //* 1
console.log(outerArr[2]()); //* 2
解释说明:
- for循环中,变量i使用let声明,具有块级作用域,
- 随着循环遍历,同时创建了三个不同的函数,他们都指向块级作用域的变量i,使得每个闭包都绑定了不同块作用域的变量
- 当内层函数在外部执行时,内层函数中使用的是遍历时块级作用域的变量i(0、1、2);
解决方案2、使用更多的闭包 —— 使用forEach、map等进行遍历,为循环变量i生成新的函数作用域
function outside() { //* 外部函数
var times = (new Array(3)).fill('');
return times.map((_, i) => { //* map函数
return function inside() { //* 内部函数
return i;
}
})
}
var outerArr = outside();
console.log(outerArr[0]()); //* 0
console.log(outerArr[1]()); //* 1
console.log(outerArr[2]()); //* 2
解释说明:
- 内层函数,使用了map中callback的变量i。为循环变量i生成新的函数作用域
- 随着循环遍历,同时创建了三个不同的callback函数,以及三个不同的内层函数,内层函数指向map中callback的变量i,生成新的闭包,
3、由于生成了新的闭包,每次循环结束,变量i不会被垃圾回收机制销毁; - 当内层函数在外部执行时,内层函数中使用的是新闭包的外层函数(map中的callback)的函数作用域变量i(0、1、2);
解决方案3、使用更多的闭包 —— 自调用函数
function outside() { //* 外部函数
var arr = [];
for (var i = 0; i < 3; i++) {
((j) => {
arr[j] = function() {
return j;
}
})(i); //* 自调用函数
}
return arr;
}
var outerArr = outside();
console.log(outerArr[0]()); //* 0
console.log(outerArr[1]()); //* 1
console.log(outerArr[2]()); //* 2
解释说明:
- 内层函数,使用了自调用函数的变量j。为循环变量j生成新的函数作用域(仿块级作用域);
- 随着循环遍历,同时创建了三个不同的自调用函数,以及三个不同的内层函数,内层函数指向自调用的变量j,生成新的闭包;
3、由于生成了新的闭包,每次循环结束,变量j不会被垃圾回收机制销毁; - 当内层函数在外部执行时,内层函数中使用的是新闭包的外层函数(自调用函数)的函数作用域变量j(0、1、2);
解决方案4、使用更多的闭包 —— 额外创建新的闭包
function makeClosure(j) { //* 制作一个闭包,返回一个callback函数
return function() {
return j;
}
}
function outside() { //* 外部函数
var arr = [];
for (var i = 0; i < 3; i++) {
arr[i] = makeClosure(i);
}
return arr;
}
var outerArr = outside();
console.log(outerArr[0]()); //* 0
console.log(outerArr[1]()); //* 1
console.log(outerArr[2]()); //* 2
解释说明:
- 内层函数,使用了新的闭包函数的变量j。为循环变量j生成新的函数作用域
- 随着循环遍历,同时创建了三个不同的makeClosure函数,以及三个不同的内层函数,内层函数指向自调用的变量j,生成新的闭包,
3、由于生成了新的闭包,每次循环结束,变量j不会被垃圾回收机制销毁; - 当内层函数在外部执行时,内层函数中使用的是新闭包的外层函数(makeClosure)的函数作用域变量j(0、1、2);