JS中For循环中嵌套setTimeout()方法的执行顺序

今天突然想到了之前看过一个在For循环中执行setTimeOut()方法的代码,那时候对输出的结果不是很理解。今天就来详细的记录下。

先贴一段简单的代码: 

function time() {
        for(var i= 0;i<5;i++){
            setTimeout(function () {
                console.log(i);
            },1000)
        }
    }
    time();

这段代码我最初看到的时候是认为结果应该是每秒会输出一个i,i的值应该是0,1,2,3,4。

但是最终的结果肯定不是这样了(不然也不会开个贴了- -)。最终的结果是5个5。

当时我人都傻了,这违背了我学代码这几年来对For循环的认知。

不过这段时间我开始明白为啥会这样了,下面开始慢慢说为啥以及解决的办法。

首先关于最开始贴的代码,我的思路是想让计算机每循环一次的时候都会进入到setTimeOut()方法里执行console.log,输出i之后再执行下一次循环。但是在JS里却并不是这样的。因为setTimeOut() 是一个异步函数,什么是异步函数呢?首先我们都知道JS的执行机制是单线程环境。什么是单线程环境?打个比方,多线程就相当于一条公路上有多个车道,一次可以通过多辆车子。单线程就相当于这条公路就只有一个车道,每次只能通过一辆车。同理,在JS的单线程环境里,每次只能从上到下一条一条的把代码执行下去。但是这样一条一条按顺序执行下去有的时候在面对特殊要求的时候速度太慢了。例如这条单车道的公路上现在要通行一辆救护车,救护车要是等到车道里的车子都通过了才能走那就太浪费时间了,不符合要求。那怎样才能让救护车以最快的速度通过这条单车道呢?这时候就引入了一个异步函数的概念。异步函数不是按正常代码那样要按顺序等前面的代码都执行完了才执行自己,当JS遇到异步函数的时候,会把异步函数插入到队列中等待。也就是所谓的插队。而setTimeOut 就是一个异步函数。所以当JS检测到setTimeOut()的时候,会把setTimeOut()插入到队列中,然后继续执行后面的代码,也就是接下来的循环。由于setTimeOut()设置了一秒后才执行,所以插入的队列位置是一秒后。而在这个一秒内for循环已经全部完成,i经过五次循环后变成了5。所以当一秒后开始执行setTimeOut()方法的时候i的值已经变成5了。因为循环了5次,所以有5次setTimeOut()方法的调用,即输出5个5。

用代码表示的话就是我们最开始设想的流程是这样的:

for(i=0) ——> console.log(0) ——>

for(i=1) ——> console.log(1) ——>

for(i=2) ——> console.log(2) ——>

for(i=3) ——> console.log(3) ——>

for(i=4) ——> console.log(4) ——>

for(i=5) ——> 执行结束

但是在实际中的流程是这样的:

for(i=0) ——> for(i=1) ——> for(i=2)

for(i=3) ——> for(i=4) ——> for(i=5)
ps:(这段循环都在一秒内完成了)
——> console.log(5) ——> console.log(5)

——> console.log(5) ——> console.log(5)

——> console.log(5) ——> 执行完成

那么有什么办法可以避免呢?

目前来看方法应该还是很多的,我目前知道的有三个,其他的方法有兴趣可以自己再百度一下。

第一个方法的思路很简单,因为setTimeOut()是异步执行,所以我们让他立即执行就可以了。

for (var i = 0; i < 5; i++) {
        (function (i) {
            setTimeout(function () {
                console.log(i);
            }, 1000 * i);
        })(i);
    }

这段函数会让JS检测到setTimeOut时不再放到队列中进行等待,而是立即运行setTimeOut()。所以能按我们所想的进行输出。

第二个方法是使用let而不是var。即:

for (let i = 0; i < 5; i++) { 
setTimeout(function() { 
console.log(i); 
}, 1000 * i); 
} 

为什么let就行而var不行呢?因为let的作用域是块作用域,所以每次JS检测到setTimeOut把setTimeOut放到队列的同时,let定义的i的值也会跟随setTimeOut进去队列。所以每次循环后队列里的setTimeOut里的i的值是不一样的。而var定义的i是无法进入setTimeOut的。i只能在运行到setTimeOut时才会向外层环境申请i的值,而这个时候i的值已经变成5了。

第三种方法是利用一切皆对象的思想,由于setTimeOut()也可以看做是一个对象,既然i值进不去setTimeOut(),那我们就额外给一个属性值j来记录i的变化,让 j = i。这样即使延迟执行了j记录的属性值也不会变化。

关于setTimeOut在for循环中的解释大概就到这了。如果有错误欢迎指正。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值