JS中For循环中嵌套setTimeout()方法的执行顺序
在For循环中执行setTimeOut()方法的代码,执行顺序是怎样的呢?
代码如下
function time() { for(var i= 0;i<5;i++){ setTimeout(function () { console.log(i); },1000) } } time();
应该会有人会说,很简单呀,for循环进行遍历,并且每次有一个输出,那结果应该是0,1,2,3,4。
其实不然,运行上诉代码之后,控制台输出如下:5个5
下面解释下为什么是5个5.
首先关于最开始贴的代码,我们是想让计算机每循环一次的时候都会进入到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了。
部分转自:https://blog.csdn.net/qq_38054172/article/details/100764630