setTimeOut的执行机制

先直接来看代码

1
2
3
4
5
for(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
2
3
4
5
6
7
8
9
10
11
for(var i=0;i<10;i++){}
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)
setTimeout(console.log(i),5000)

setTimeout属于异步执行函数,js遇到setTimeout时,会将该函数放入等待队列,等待当前主线程执行完毕以后开始执行setTimeout,如果有多个setTimeout,都会放到等待队列。

那么这里队列里的多个setTimeout谁先执行呢?就是根据setTimeout里的第二个参数(延时时间)决定的,
最后,给出一个很小但是仍然在困扰我的一个问题,希望有兴趣的小伙伴可以跟我一起研究:

1
2
3
setTimeout(function(){while(true){}},6000);
setTimeout(function(){console.log(1)},10000);
setTimeout(function(){console.log(2)},5000);

语句执行,立即插入队列。同步任务执行完后5秒,开始第一个异步任务,输出2。第6秒开始第二个异步任务,死循环。因为第二个任务一直没有完成,所以第三个任务输出1会一直等待,并不会在第10秒准时开始。


上一篇
数组分割方法splice()和slice() 数组分割方法splice()和slice()
在某些时候,我们得到一个数组,需要每隔几位将数组拆分成新数组。 使用splicesplice()方法,向数组中添加/删除元素,返回被删除的元素组成的数组arr.splice(index,num,item1,…itemX) 注意,splice
2016-08-15 刘赛
下一篇
正则表达式1-定义正则 正则表达式1-定义正则
定义正则表达式有两种方法 字面量方式,注意加/ / 12var reg=/\bis\b/g;"This is LILI, he is 18".replace(reg,"0") Regexp构造函
2016-07-20 刘赛