关于for循环里面异步操作的问题

https://www.cnblogs.com/vipzhou/p/6519552.html

 

关于for循环里面异步操作的问题

 

首先来看一个比较简单的问题,我们想实现的就是每隔1s输出0-4的值,就是这么简单,看下错误写法:

1
2
3
4
5
6
7
8
function test() {
     for  ( var  i = 0; i < 5; ++i) {
         setTimeout(function() {
             console.log( "index is :" , i);
         }, 1000);
     }
}
test();

以上代码会如何输出?输出如下:

1
2
3
4
5
index  is  : 5
index  is  : 5
index  is  : 5
index  is  : 5
index  is  : 5

而且该操作几乎是在同一时间完成,setTimeout定时根本就没有起作用,这是因为:单线程的js在操作时,对于这种异步操作,会先进行一次“保存”,等到整个for循环执行结束后,此时i的值已经变成5,因为setTimeout是写在for循环中的,相当于存在5次定时调用,这5次调用均是在for循环结束后进行的,所以自然而然输出都是5,正确的实现有几种,一般情况下,我们使用递归实现,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// var i = 0;
// var arr = [0, 1, 2, 3, 4];
 
// function box6() {
//     if (i < arr.length) {
//         setTimeout(function() {
//             console.log("index is : ", i);
//             i++;
//             box6();
//         }, 1000);
//     }
// }
 
box6();
 
function box7(param) {
     if  (param < 5) {
         console.log( "index is :" , param);
         setTimeout(function() {
             box7(param + 1);
         }, 1000)
     }
}
box7(0);

正确实现每隔1s打印输出如下:

1
2
3
4
5
index  is  : 0
index  is  : 1
index  is  : 2
index  is  : 3
index  is  : 4

使用递归实现的倒计时:

1
2
3
4
5
6
7
8
9
10
11
12
13
function showTime(count) {
     console.log( "count is : " , count);
     if  (count == 0) {
         console.log( "All is Done!" );
     else  {
         count -= 1;
         setTimeout(function() {
             showTime(count);
         }, 1000);
     }
}
 
showTime(20);

递归调用很好的解决了setTimeout同时执行的情况,如果使用async、await实现,可以如下写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var  asyncFunc = function(arr, i) {
     return  new  Promise(function(resolve, reject) {
         setTimeout(function() {
             arr.push(i);
             console.log( "index is : " , i);
             resolve();
         }, 1000);
     });
}
 
var  box5 = async function() {
     var  arr = [];
     for  ( var  i = 0; i < 5; i++) {
         await asyncFunc(arr, i);
     }
     console.log(arr);
}
 
box5();

同样实现每隔1s正确地打印输出如下:

1
2
3
4
5
6
index  is  :  0
index  is  :  1
index  is  :  2
index  is  :  3
index  is  :  4
[ 0, 1, 2, 3, 4 ]

接下来再看个需求:构建一个函数数组,该数组每一项函数的功能是依次输出0-4,错误写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function buildList(list) {
     var  result = [];
     for  ( var  i = 0; i < list.length; i++) {
         var  item =  'item'  + list[i];
         result.push(function() { console.log(item +  ' '  + list[i]) });
     }
     return  result;
}
 
function testList() {
     var  fnlist = buildList([1, 2, 3]);
     for  ( var  j = 0; j < fnlist.length; j++) {
         fnlist[j]();
     }
}
 
testList();

输出如下:

1
2
3
item3 undefined
item3 undefined
item3 undefined

for循环里面使用匿名函数和直接写setTimeout调用比较类似,但是这里又有点不同,for循环执行结束后,匿名函数开始调用,发现里面存在“item”变量,这时依次会向上级查找,恰好找到循环结束时的item变量值为“list[2]”即为3,item为3但是i的值已经变为3,又因为list[3]的值为undefined,所以这里输出3遍item3 undefined。不信可以修改下数组如下,其余代码不变:

1
2
3
4
5
6
7
8
9
10
function buildList(list) {
     ...
}
 
function testList() {
     var  fnlist = buildList([6, 7, 8]);
     ...
}
 
testList();

这里绝对输出的是:

1
2
3
item8 undefined
item8 undefined
item8 undefined

再来看下正确的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function buildList(list) {
     var  result = [];
     for  ( var  i = 0; i < list.length; i++) {
         var  item =  'item'  + list[i];
         result.push(function(index, it) {
             return  function() {
                 console.log(it +  ' '  + list[index]);
             }
         }(i, item));
     }
     return  result;
}
 
function testList() {
     var  fnlist = buildList([6, 7, 8]);
     for  ( var  j = 0; j < fnlist.length; j++) {
         fnlist[j]();
     }
}
 
testList();

输出如下:

1
2
3
item6 6
item7 7
item8 8

这里主要使用的是即时执行函数,什么是即时执行函数?可以理解为一个封闭的代码块,该代码块中的代码会在定义时立即执行一遍,各个代码块的作用域彼此独立,不会污染外部环境,写法其实有很多种,上面只是一种,同样的还有使用void、+、-、!等等,jquery源码就是直接使用的这里的圆括号写法的这种。

再看几个测试例子:

1
2
3
4
5
6
7
8
9
10
function box2() {
     var  arr = [];
     for  ( var  i = 0; i < 5; i++) {
         arr[i] = (function(num) {  //自我执行,并传参(将匿名函数形成一个表达式)(传递一个参数)
             return  num;  //这里的num写什么都可以                   
         })(i);  //这时候这个括号里面的i和上面arr[i]的值是一样的都是取自for循环里面的i                           
     }
     return  arr;
}
console.log(box2());      //[ 0, 1, 2, 3, 4 ]
1
2
3
4
5
6
7
8
9
10
11
12
function box4() {
     var  arr = [];
     for  ( var  i = 0; i < 5; i++) {
         arr[i] = (function(num) {  //自我执行,并传参(将匿名函数形成一个表达式)(传递一个参数),在这个闭包里面再写一个匿名函数
             return  function() {
                 return  num;
             }
         })(i);  //这时候这个括号里面的i和上面arr[i]的值是一样的都是取自for循环里面的i                     
     }
     return  arr;
}
console.log(box4());    //[ [Function], [Function], [Function], [Function], [Function] ]

box4这种写法其实跟上面有一种是一致的,就不多说了,其实主要就是闭包,稍微改变一下代码,实现的结果却截然不同,共勉吧。。。

 

转载于:https://www.cnblogs.com/chaoyuehedy/p/10658804.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值