先直接来看代码1
2
3
4
5for(var i=0;i<10;i++){
setTimeout(function(){
console.log(i);//打印十个10
},5000)
}
从结果上来看,for循环执行完跳出之后,才开始执行setTimeout(所有i才等于10),为什么不是每次迭代都执行一次setTimeout呢?
这是因为,在ES6之前,js是没有块级作用域的,这就意味着,在for循环中定义的变量i,其实是属于全局的,即在全局范围内都可以被访问到,所以每次for循环都在更新这个变量i。
为什么整个for循环会先于setTimeout执行,而不是一次循环执行一次?
因为js是单线程的,单线程意味着,所有任务需要排队,前一个任务结束,才会执行下一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
为了优化单线程的性能,js将任务分为两种,一种是同步任务(sync),一种是异步任务(async)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入任务队列的任务,只有主线程的同步任务执行完毕,异步任务才会进入执行队列执行。只要主线程空了,就会去读取任务队列,这就是js的运行机制。这个过程会不断重复。
而setTimeout,就被JavaScript定义为异步任务。每次for循环的迭代,都将setTimeout中的回调函数加入任务队列等待执行。也就是说,只有同步任务中的for循环完全结束,主线程中才会去任务队列中找到尚未执行的十个setTimeout(十次迭代)回调函数并顺序执行(先进先出)。而此时,i已经经过循环结束变成了10,所以,此时主线程执行的,是十个一摸一样的打印i的回调函数,即打印十个10。
1 | for(var i=0;i<10;i++){} |
setTimeout属于异步执行函数,js遇到setTimeout时,会将该函数放入等待队列,等待当前主线程执行完毕以后开始执行setTimeout,如果有多个setTimeout,都会放到等待队列。
那么这里队列里的多个setTimeout谁先执行呢?就是根据setTimeout里的第二个参数(延时时间)决定的,
最后,给出一个很小但是仍然在困扰我的一个问题,希望有兴趣的小伙伴可以跟我一起研究:
1 | setTimeout(function(){while(true){}},6000); |
语句执行,立即插入队列。同步任务执行完后5秒,开始第一个异步任务,输出2。第6秒开始第二个异步任务,死循环。因为第二个任务一直没有完成,所以第三个任务输出1会一直等待,并不会在第10秒准时开始。