JS中的for循环——你可能不知道的点。

 
 

640?wx_fmt=gif

 
 

提出问题

问题1:

看一段for循环的代码,大家先想一下执行结果是什么?

var arr = [2,4,6,8,10];var arrLength = arr.length;for (var i = 0; i < arrLength; i++) {    setTimeout(function() {        console.log(i);        console.log(arr[i]);    }, 2000);}2,4,6,8,10];
var arrLength = arr.length;

for (var i = 0; i < arrLength; i++) {
setTimeout(function() {
console.log(i);
console.log(arr[i]);
}, 2000);
}

问题2:

for循环中出现多个异步函数(比如ajax请求,或者node后端执行一些数据库操作或文件操作),如果想要这些异步串行变为同步应该怎么做?

问题1解决与相关讲解

结果

预期结果

0 2  1 4  2 6  3 8  4 102  1 4  2 6  3 8  4 10

运行后的结果

5 undefined  5 undefined  5 undefined  5 undefined  5 undefinedundefined  5 undefined  5 undefined  5 undefined  5 undefined

产生结果的原因

setTimeout()函数回调属于异步任务,会出现在宏任务队列中,被压到了任务队列的最后,在这段代码应该是for循环这个同步任务执行完成后才会轮到它,所以for循环在遍历过程中i不断加1,直到i判断失败一次才停止,这时候i为5,也就是说空跑了5次循环。等到了setTimeOut预定的时间后就会执行在for遍历过程中声明的5个setTimeout。所以最终运行后会出现上面的结果,与预期结果不符。

注:关于宏任务队列,同步任务等相关的问题,如果有问题,可以查看我的另一篇文章一道面试题引发的事件循环深入思考详细了解。

正确执行的解决方案

1. 闭包,立即执行函数

想要得到预期的结果,第一种办法是使用闭包,在闭包函数内部形成了局部作用域,每循环一次,形成一个自己的局部作用域,不受外部变量变化的影响。 代码如下:

var arr = [2,4,6,8,10];var arrLength = arr.length;for (var i = 0; i < arrLength; i++) {    (function(i) {        setTimeout(function() {            console.log('i是' + i);            console.log('value是' + arr[i]);        }, 2000);    })(i);}2,4,6,8,10];
var arrLength = arr.length;

for (var i = 0; i < arrLength; i++) {
(function(i) {
setTimeout(function() {
console.log('i是' + i);
console.log('value是' + arr[i]);
}, 2000);
})(i);
}
2. let

将代码中的var改成let,let非常适合用于 for循环内部的块级作用域。JS中的for循环体比较特殊,每次执行都是一个全新的独立的块作用域,用let声明的变量传入到 for循环体的作用域后,不会发生改变,不受外界的影响。

代码如下:

var arr = [2,4,6,8,10];var arrLength = arr.length;// i虽然在全局作用域声明,但是在for循环体局部作用域中使用的时候,变量会被固定,不受外界干扰。for (let i = 0; i < arrLength; i++) {    (function(i) {        setTimeout(function() {            console.log('i是' + i);            console.log('value是' + arr[i]);        }, 2000);    })(i);}2,4,6,8,10];
var arrLength = arr.length;
// i虽然在全局作用域声明,但是在for循环体局部作用域中使用的时候,变量会被固定,不受外界干扰。
for (let i = 0; i < arrLength; i++) {
(function(i) {
setTimeout(function() {
console.log('i是' + i);
console.log('value是' + arr[i]);
}, 2000);
})(i);
}

问题2解决与相关讲解

for循环中使用异步,在node.js后端开发或者前端ajax请求的时候还是比较常见的。有多种解决方案

  1. 回调 callback 嵌套异步操作、再回调的方式

  2. Promise + then() 层层嵌套

  3. async和await

选择我个人认为最优秀的解决方式3async和await进行讲解。

async + await “外异内同”

例子:

如果要去将一批数据发送到服务器,只有前一批发送成功(即服务器返回成功的响应),才开始下一批数据的发送,否则终止发送。这就是一个典型的 “for 循环中存在相互依赖的异步操作” 的例子

例子对应伪代码:

async function task () {    for (let val of [1, 2, 3, 4]) {        // await 是要等待响应的        let result = await send(val);        if (!result) {            break;        }    }}task();function task () {
for (let val of [1, 2, 3, 4]) {
// await 是要等待响应的
let result = await send(val);
if (!result) {
break;
}
}
}
task();

伪代码中使用await之后,实现了异步变成同步的转化,只有for循环中当次对应的发送请求完成且获取结果,才会继续往下执行。

await几点说明:

当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句。

我对阮一峰老师的话再具体说明一下,可能有些同学还不是特别理解。实际上我们调用了await,这时候await这条语句下面的语句已经不会执行了(内同),而是先给外层async函数返回了一个promise对象,await后面对应的应该也是一个promise对象只有该对象 resolve 掉,产生结果,await 那一行代码才算真正执行完,才继续往下走。(注意:await执行之后应该是一个resolve的结果而不是promise对象了)。

node.js后端开发-await在for循环中的应用

看一段后端项目中应用await的代码:

//dayResult是一个查询到的数组for (const item of dayResult)    {        //TODO 查询用户vip表 用户体验vip距离到期的用户列表        let userIds=await db.vip.findAll({            where:{                experience_time:{                    '$lte':moment().subtract(15-item.day,'day').endOf('day') ,//获取四天前都0时0分秒                    '$gte':moment().subtract(15-item.day,'day').startOf('day') ,//获取四天前都0时0分秒                        },                        vip_type:0                    },                attributes:['user_id',Sequelize.literal(`'${item.id}' as notice_id`)],                raw:true            });        userNoticeRecord=userNoticeRecord.concat(userIds)        }
for (const item of dayResult)
{
//TODO 查询用户vip表 用户体验vip距离到期的用户列表
let userIds=await db.vip.findAll({
where:{
experience_time:{
'$lte':moment().subtract(15-item.day,'day').endOf('day') ,//获取四天前都0时0分秒
'$gte':moment().subtract(15-item.day,'day').startOf('day') ,//获取四天前都0时0分秒
},
vip_type:0
},
attributes:['user_id',Sequelize.literal(`'${item.id}' as notice_id`)],
raw:true
});
userNoticeRecord=userNoticeRecord.concat(userIds)
}

总结

在学习的过程中,多思考,不要只记住结果,多想一些为什么?嘿嘿,如果有问题希望指出。

640?wx_fmt=png

一道面试题引发的事件循环深入思考

优雅简洁的异步Asnyc/Await

回调地狱解决方案之Promise

javascript数组常用函数与实战总结


640?wx_fmt=jpeg



觉得本文对你有帮助?请分享给更多人

关注『程序员成长指北』,一起提升开发技能




640?wx_fmt=png



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值