因为自己在平时工作中,有些功能需要用到定时器,但是定时器并不像我们表边上看到的那样,所以这周末我看看书查查资料,深入研究了一下JavaScript中的定时器,那么废话不多说,下面进入我们今天的正题。
大家都知道JavaScript是单线程的,所以不管是定时器还是用户的操作都是需要在线程队列中排队执行的。
一、定时器在执行线程队列里的分析
为了更好的理解我还是直接写个测试代码来看一下(由于有人反映例子不是很直观不好理解,所以我做了一个新的例子,修改于2016年8月29日),这样分析起来更直观一些
1 <meta charset="utf-8"> 2 </head> 3 <body> 4 <button >开始</button> 5 <script> 6 var oBtn1= document.getElementById("btn1"); 7 function createEle(num){ 8 for (i=0;i<num;i++){ 9 var oDiv = document.createElement("div"); 10 oDiv.innerHTML = i+1; 11 } 12 } 13 oBtn1.addEventListener("click",function(){ 14 var time1 = new Date().getTime(); 15 createEle(20000); 16 var time2 = new Date().getTime(); 17 console.log("普通函数1执行完的时间:"+(time2-time1)); 18 setTimeout(function(){ 19 var time3 = new Date().getTime(); 20 console.log("第一个setTimeout里面开始执行的时间:"+(time3-time1)); 21 createEle(20000); 22 var time4 = new Date().getTime(); 23 console.log("setTimeout里面的函数运行时间:"+(time4-time3)); 24 },50); 25 setInterval(function(){ 26 var time5 = new Date().getTime(); 27 console.log("setInterval开始执行的时间:"+(time5-time1)); 28 createEle(20000); 29 },50); 30 setTimeout(function(){ 31 var time6 = new Date().getTime(); 32 console.log("第二个setTimeout里面开始执行的时间:"+(time6-time1)); 33 createEle(20000); 34 },100); 35 createEle(20000); 36 var time7 = new Date().getTime(); 37 console.log("普通函数2执行完的时间:"+(time7-time1)); 38 },false); 39 40 </script> 41 </body> 42 </html>
在上面这段测试代码里面一共分为5个部分:
1.创建dom节点
2.第一个setTimeout函数延迟50ms
3.setInterval函数每隔50ms
4.第二个setTimeout函数延迟100ms
5.创建dom节点
没有基础的人一般认为都是按顺序执行呗,这有啥好说的,有点基础的人会认为先执行两个创建dom节点的函数,再延迟50ms执行第一个setTimeout里面的内容,并且每隔50ms会触发setInterval,然后延迟100ms执行第二个setTimeout里面的内容。但是事实并是这样的!!!
下面我贴出代码的运行结果来逐一的分析
从运行结果可以看出,首先结果打印普通函数1运行需要花费170ms(多次刷新页面可能这个时间会不一样,所以我们先姑且认为这个普通函数执行需要170ms)。下面接着打印了普通函数2运行需要花费170ms,然后下面才是定时器的执行时间打印结果。到这里就可以看出我要证明的第一个结论:1.js中的定时器是在当前函数里,所有普通函数执行完成才开始执行定时器的。
下面我再具体分析一下定时器在这期间的运行过程,来揭示下面的结论。
在程序里执行到170ms的时候,第一个普通函数执行完,这时由于还有其他普通函数的存在,所以setTimeout和setInterval都进入等待队列并且开始计时,在220ms的时候由于还有普通函数2在执行占用着线程,所以setTimeout里面的函数无法执行,并且setInterval被二次触发,但是也因为有其他普通函数占用着线程,所以依旧在队列中等待执行。
从第二行执行结果可以看出在350ms的时候第二个普通函数已经执行完成,至此所有定时器以外的函数全部执行完毕,从第三行执行结果可以看出在351ms的时候第一个setTimeout立刻被执行,而并没有再等待50ms,这是因为上面已经提到了第一个setTimeout在第一个普通函数执行完的170ms的时候就开始计时了,只是因为50ms之后,第二个普通函数依然占用着线程导致setTimeout无法执行,所以在所有定时器以外的函数全部执行完毕之后,第一个定时器立刻开始执行里面的内容。这也就是我要证明的第二个结论:2.定时器即使指定了时间,也不一定就能在指定的时间执行(只能比设置的时间长,不会比设置的时间短)。
有人可能会想那么351ms的时候setInterval也应该被触发之后每50ms触发一次,但是在351ms的时候setInterval并没有被执行,这是因为第一个setTimeout里面的函数还在执行,所以setInterval还是在等待队列中等待,在514ms的时候第一个setTimeout里面的函数执行完毕,则开始触发setInterval。这是因为设置的时间都是相同的(50ms),所以就是按定时器函数写的顺序来执行的。
如果我们把setInterval改成30ms的话,那么在第一个setTimeout之前是会先执行一次setInterval的。代码如下:
1 <html> 2 <head> 3 <title>JavaScript定时器test</title> 4 <meta charset="utf-8"> 5 </head> 6 <body> 7 <button >开始</button> 8 <script> 9 var oBtn1= document.getElementById("btn1"); 10 function createEle(num){ 11 for (i=0;i<num;i++){ 12 var oDiv = document.createElement("div"); 13 oDiv.innerHTML = i+1; 14 } 15 } 16 oBtn1.addEventListener("click",function(){ 17 var time1 = new Date().getTime(); 18 createEle(20000); 19 var time2 = new Date().getTime(); 20 console.log("普通函数1执行完的时间:"+(time2-time1)); 21 setTimeout(function(){ 22 var time3 = new Date().getTime(); 23 console.log("第一个setTimeout里面开始执行的时间:"+(time3-time1)); 24 createEle(20000); 25 var time4 = new Date().getTime(); 26 console.log("setTimeout里面的函数运行时间:"+(time4-time3)); 27 },50); 28 setInterval(function(){ 29 var time5 = new Date().getTime(); 30 console.log("setInterval开始执行的时间:"+(time5-time1)); 31 createEle(20000); 32 },30); 33 setTimeout(function(){ 34 var time6 = new Date().getTime(); 35 console.log("第二个setTimeout里面开始执行的时间:"+(time6-time1)); 36 createEle(20000); 37 },100); 38 createEle(20000); 39 var time7 = new Date().getTime(); 40 console.log("普通函数2执行完的时间:"+(time7-time1)); 41 },false); 42 43 </script> 44 </body> 45 </html>