javascript疑难问题---8、闭包在循环中的问题
一、总结
一句话总结:
闭包在循环中可能会遇到闭包调用的变量是循环执行完之后的变量,比如演示中的输出结果都是“元素5”,可以通过 【让匿名函数立即执行】 或 【用闭包局部变量常驻内存的特点来保存中间值的方法】 来解决这个问题
/* * 现象:数组里面的匿名函数的执行结果 都是 元素5 * 原因:匿名函数执行的时候,外层f1()函数已经执行完了, * 循环中i的值是5,而匿名函数执行的时候找的i的值就是循环中i的值, * 所以也是5 * */ // function f1(){ // var arr=[]; // for(var i=0; i<5; i++){ // arr[i]=function () { // return '元素'+i; // }; // } // return arr; // } // // console.log(f1()); // // console.log(f1()[0]); // var arr1=f1(); // //arr1[0](); // console.log(arr1[0]()); // console.log(arr1[1]()); // console.log(arr1[2]()); // console.log(arr1[3]()); // console.log(arr1[4]()); /*解决方法一: * 让匿名函数立即执行 * */ // function f1(){ // var arr=[]; // for(var i=0; i<5; i++){ // arr[i]=(function () { // return '元素'+i; // })(); // } // return arr; // } // console.log(f1()); /* * 解决方法二: * 用闭包局部变量常驻内存的特点来保存中间值, * */ function f1(){ var arr=[]; for(var i=0; i<5; i++){ arr[i]=(function (i) { return function () { return '元素'+i; }; })(i); } return arr; } console.log(f1()); console.log(f1()[0]); var arr1=f1(); console.log(arr1[0]()); console.log(arr1[1]()); console.log(arr1[2]()); console.log(arr1[3]()); console.log(arr1[4]());
二、闭包在循环中的问题
博客对应课程的视频位置:8、闭包在循环中的问题
https://www.fanrenyi.com/video/4/160
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>闭包在循环中的问题</title> 6 </head> 7 <body> 8 <!-- 9 10 --> 11 <script> 12 // function f1(){ 13 // var arr=[]; 14 // for(var i=0; i<5; i++){ 15 // arr[i]='元素'+i; 16 // } 17 // return arr; 18 // } 19 // console.log(f1()); 20 21 22 /* 23 * 现象:数组里面的匿名函数的执行结果 都是 元素5 24 * 原因:匿名函数执行的时候,外层f1()函数已经执行完了, 25 * 循环中i的值是5,而匿名函数执行的时候找的i的值就是循环中i的值, 26 * 所以也是5 27 * */ 28 // function f1(){ 29 // var arr=[]; 30 // for(var i=0; i<5; i++){ 31 // arr[i]=function () { 32 // return '元素'+i; 33 // }; 34 // } 35 // return arr; 36 // } 37 // // console.log(f1()); 38 // // console.log(f1()[0]); 39 // var arr1=f1(); 40 // //arr1[0](); 41 // console.log(arr1[0]()); 42 // console.log(arr1[1]()); 43 // console.log(arr1[2]()); 44 // console.log(arr1[3]()); 45 // console.log(arr1[4]()); 46 47 /*解决方法一: 48 * 让匿名函数立即执行 49 * */ 50 // function f1(){ 51 // var arr=[]; 52 // for(var i=0; i<5; i++){ 53 // arr[i]=(function () { 54 // return '元素'+i; 55 // })(); 56 // } 57 // return arr; 58 // } 59 // console.log(f1()); 60 61 /* 62 * 解决方法二: 63 * 用闭包局部变量常驻内存的特点来保存中间值, 64 * */ 65 function f1(){ 66 var arr=[]; 67 for(var i=0; i<5; i++){ 68 arr[i]=(function (i) { 69 return function () { 70 return '元素'+i; 71 }; 72 })(i); 73 } 74 return arr; 75 } 76 console.log(f1()); 77 console.log(f1()[0]); 78 var arr1=f1(); 79 console.log(arr1[0]()); 80 console.log(arr1[1]()); 81 console.log(arr1[2]()); 82 console.log(arr1[3]()); 83 console.log(arr1[4]()); 84 </script> 85 </body> 86 </html>
三、参考资料:JS中for循环里面的闭包问题的原因及解决办法
转自或参考:JS中for循环里面的闭包问题的原因及解决办法
https://blog.csdn.net/weixin_40333655/article/details/90905514
我们先看一个正常的for循环,普通函数里面有一个for循环,for循环结束后最终返回结果数组
function box(){
var arr = [];
for(var i=0;i<5;i++){
arr[i] = i;
}
return arr;
}
//alert 输出一个弹出框
alert(box()) //正常情况不需要闭包,就可以达到预期效果,输出结果为一个数组0,1,2,3,4
有时我们需要在for循环里面添加一个匿名函数来实现更多功能,看下面代码
//循环里面包含闭包函数
function box(){
var arr = [];
for(var i=0;i<5;i++){
arr[i] = function(){
return i; //由于这个闭包的关系,他是循环完毕之后才返回,最终结果是4++是5
} //这个匿名函数里面根本没有i这个变量,所以匿名函数会从父级函数中去找i,
} //当找到这个i的时候,for循环已经循环完毕了,所以最终会返回5
//另外循环体内声明了一个匿名函数,而这个匿名函数并没有得到执行,所以arr数组每个元素都是一个匿名函数 function(){return i}
return arr;
}
//alert(box()); //执行5次匿名函数本身
//alert(box()[1]); //执行第2个匿名函数本身
//alert(box().length); //最终返回的是一个数组,数组的长度为5
alert(box()[0]()); //数组中的第一个数返回的是5,这是为什么?
上面这段代码就形成了一个闭包:
闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见的方式,就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。
在for循环里面的匿名函数执行 return i
语句的时候,由于匿名函数里面没有i这个变量,所以这个i他要从父级函数中寻找i,而父级函数中的i在for循环中,当找到这个i的时候,是for循环完毕的i,也就是5,所以这个box得到的是一个数组[5,5,5,5,5]。
解决方案1
在看解决方案一之前,我们先看一下匿名函数的自我执行:
匿名函数自我执行的写法是,在函数体外面加一对圆括号,形成一个表达式,在圆括号后面再加一个圆括号,里面可传入参数。
例如下代码:
(function(){
alert('lee'); //匿名函数自我执行(匿名函数)()
})();
我们再来看解决方案1:
function box(){
var arr = [];
for(var i=0;i<5;i++){
arr[i] = (function(num){ //自我执行,并传参(将匿名函数形成一个表达式)(传递一个参数)
return num; //这里的num写什么都可以
})(i); //这时候这个括号里面的i和上面arr[i]的值是一样的都是取自for循环里面的i
}
return arr;
}
//alert(box());
//alert(box()[1]);
//alert(box().length);
alert(box()[0]);
通过给匿名函数传参,而传递的这个参数i是每次执行for循环里面的i,每次传递的参数i的值都不一样,匿名函数里面的num接收传递的参数i,所以box()最终输出结果为[0,1,2,3,4]
解决方案2
这种方案的原理就是在匿名函数1里面再写入一个匿名函数2,这个匿名函数2需要的num值会在他的父级函数匿名函数1里面去寻找,而匿名函数1里面的num值就是传入的这个参数i,和上面例子中的i是一样的,
function box(){
var arr = [];
for(var i=0;i<5;i++){
arr[i] = (function(num){
//num在这里 //原理和上面一种方法一样的,所以可以实现闭包
return function(){ //在这个闭包里面再写一个匿名函数
return num;
};
})(i)
}
return arr;
}
//alert(box());
//alert(box()[1]);
//alert(box().length);
var b = box();
alert(b[0]());
alert(box()[0]());
box()最终返回结果[0,1,2,3,4],
解决方案3
如果将一个匿名函数自我执行的时候赋值给一个变量,那么这个匿名函数中的圆括号的可以去掉的,看下面代码,
var tip = function(){ //这样把匿名函数自我执行的时候赋值给一个变量,那么圆括号是可以去掉的
alert('lee');
}();
利用匿名函数的这一特点,我们可以将解决方案1中的代码改进一下:
function box(){
var arr = [];
for(var i=0;i<5;i++){
arr[i] = function(num){
return num;
}(i);
}
return arr;
}
//alert(box());
//alert(box()[1]);
//alert(box().length);
alert(box()[4]);
匿名函数在执行的时候他本身就传递给了一个变量arr[i],所以匿名函数的圆括号是可以去掉的。
以上就是几种解决for循环中闭包的办法,当然还有更多办法,大家可自行google或者bing其他资料来加深印象(百度是基本上搜不到什么有用的文章的)。