闭包
返回函数不要引用任何循环变量,或者后续会发生变化的变量。
=. =
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值(函数作为返回值
),形成闭包
,无论该循环变量后续如何更改,已绑定到函数参数的值不变。
DEMO1
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push(function () {
return i * i;
});
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都添加到一个Array中返回了。
你可能认为调用f1(),f2()和f3()
结果应该是1,4,9
,但实际结果是:
f1(); // 16
f2(); // 16
f3(); // 16
全部都是16
!原因就在于返回的函数引用了变量i,但它并非立刻执行
。等到3个函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16。
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量
。
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push((function (n) {
return function () {
return n * n;
}
})(i));
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
f1(); // 1
f2(); // 4
f3(); // 9
DEMO2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<!-- 在页面添加三个按钮 -->
<input type="button" value="1">
<input type="button" value="2">
<input type="button" value="3">
<script>
//by zhengkai.blog.csdn.net
// 获取页面所有的input
var allBtn = document.getElementsByTagName('input');
//请自己取消三段注释,感受一下效果
//循环绑定点击事件,当然毫无疑问这里点击之后会弹出3,
//因为我们知道js是单线程的,当基本逻辑语句执行完之后,才会执行点击事件
//而当你触发点击事件的时候,for循环都已经跑完了,所以i已经变成了3;
for(var i = 0; i < allBtn.length; i++){
console.log("allBtn["+i+"]");
allBtn[i].onclick = function(){
console.log("alert("+i+")");
}
}
// 解决方法1:也是最简单的方法,就是将for循环的var变成let
//这样当点击每个按钮的时候,就会依次弹出0,1,2;
//let是ES6新增的一个变量声明方式,拥有块级作用域;
// for(let i = 0; i < allBtn.length; i++){
// console.log("allBtn["+i+"]");
// allBtn[i].onclick = function(){
// console.log("alert("+i+")");
// }
// }
//解决方法2:利用闭包(说是闭包,貌似也不完全是),也就是函数作用域访问原则的特性
//函数内部可以访问外部的变量,外部却访问不了里边的;
// for(var i = 0; i < allBtn.length; i++){
// console.log("allBtn["+i+"]");
// (function(i){
// allBtn[i].onclick = function(){
// console.log("alert("+i+")");
// }
// })(i);
// }
</script>
</body>
</html>
ConsoleOutput
经典问题,肯定是有问题的啦,控制台输出了alert(3)
.
因为我们知道js是单线程的,当基本逻辑语句执行完之后,才会执行点击事件,而当你触发点击事件的时候,for循环都已经跑完了,所以i已经变成了3
allBtn[0]
allBtn[1]
allBtn[2]
alert(3)
alert(3)
alert(3)
优化后的let
方法或者闭包
方法输出如下
allBtn[0]
allBtn[1]
allBtn[2]
alert(0)
alert(1)
alert(2)