因为自己在平时工作中,有些功能需要用到定时器,但是定时器并不像我们表边上看到的那样,所以这周末我看看书查查资料,深入研究了一下JavaScript中的定时器,那么废话不多说,下面进入我们今天的正题。
大家都知道JavaScript是单线程的,所以不管是定时器还是用户的操作都是需要在线程队列中排队执行的。
一、定时器在执行线程队列里的分析
为了更好的理解我还是直接写个测试代码来看一下(由于有人反映例子不是很直观不好理解,所以我做了一个新的例子,修改于2016年8月29日),这样分析起来更直观一些
<meta charset="utf-8">
</head>
<body>
<button id="btn1">开始</button>
<script>
var oBtn1= document.getElementById("btn1");
function createEle(num){
for (i=0;i<num;i++){
var oDiv = document.createElement("div");
oDiv.innerHTML = i+1;
}
}
oBtn1.addEventListener("click",function(){
var time1 = new Date().getTime();
createEle(20000);
var time2 = new Date().getTime();
console.log("普通函数1执行完的时间:"+(time2-time1));
setTimeout(function(){
var time3 = new Date().getTime();
console.log("第一个setTimeout里面开始执行的时间:"+(time3-time1));
createEle(20000);
var time4 = new Date().getTime();
console.log("setTimeout里面的函数运行时间:"+(time4-time3));
},50);
setInterval(function(){
var time5 = new Date().getTime();
console.log("setInterval开始执行的时间:"+(time5-time1));
createEle(20000);
},50);
setTimeout(function(){
var time6 = new Date().getTime();
console.log("第二个setTimeout里面开始执行的时间:"+(time6-time1));
createEle(20000);
},100);
createEle(20000);
var time7 = new Date().getTime();
console.log("普通函数2执行完的时间:"+(time7-time1));
},false); </script>
</body>
</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的。代码如下:
<html>
<head>
<title>JavaScript定时器test</title>
<meta charset="utf-8">
</head>
<body>
<button id="btn1">开始</button>
<script>
var oBtn1= document.getElementById("btn1");
function createEle(num){
for (i=0;i<num;i++){
var oDiv = document.createElement("div");
oDiv.innerHTML = i+1;
}
}
oBtn1.addEventListener("click",function(){
var time1 = new Date().getTime();
createEle(20000);
var time2 = new Date().getTime();
console.log("普通函数1执行完的时间:"+(time2-time1));
setTimeout(function(){
var time3 = new Date().getTime();
console.log("第一个setTimeout里面开始执行的时间:"+(time3-time1));
createEle(20000);
var time4 = new Date().getTime();
console.log("setTimeout里面的函数运行时间:"+(time4-time3));
},50);
setInterval(function(){
var time5 = new Date().getTime();
console.log("setInterval开始执行的时间:"+(time5-time1));
createEle(20000);
},30);
setTimeout(function(){
var time6 = new Date().getTime();
console.log("第二个setTimeout里面开始执行的时间:"+(time6-time1));
createEle(20000);
},100);
createEle(20000);
var time7 = new Date().getTime();
console.log("普通函数2执行完的时间:"+(time7-time1));
},false); </script>
</body>
</html>
修改之后的执行结果如下图所示:
从图上结果也是可以引出我要说的第三个结论:3.定时器如果设置的时间都相同,是按照定时器函数写的顺序来执行的,如果设置的时间不同,则是时间设置小的定时器函数会先执行,时间设置大的后执行。
接着之前的分析,在setInterval被触发之后,由于setInterval里面的函数没有执行完(也是需要170ms),所以第二个setTimeout函数没法立即被执行,需要等待setInterval里面的函数执行完毕之后,这也是再一次印证了我上面的结论2,后面就是触发第二个setTimeout函数,等第二个setTimeout函数里面的函数执行完下面依次执行setInterval。
小结:
1.js中的定时器是在当前函数里,所有普通函数执行完成才开始执行定时器的。
2.定时器即使指定了时间,也不一定就能在指定的时间执行(只能比设置的时间长,不会比设置的时间短)。
3.定时器如果设置的时间都相同,是按照定时器函数写的顺序来执行的,如果设置的时间不同,则是时间设置小的定时器函数会先执行,时间设置大的后执行。
二、setTimeout和setInterval之间的区别
还是直接上代码来进行对比
setTimeout(function cb(){
console.log("setTimeout");
setTimeout(cb,10);
},10);
setInterval(function(){
console.log("setInterval");
},10);
上面的代码看上去功能似乎是一样的,实际上两者是有区别的,在setTimeout里要等里面的函数执行完(也就是前一个callback)再延迟10ms(甚至更久)才可以再次执行回调函数,而setInterval是每隔10ms就会去执行函数里面的内容,而不去管上一个是否执行完。这就是两者之间的区别。
三、定时器延迟时间的可靠性
看了我第一部分贴出的运行结果之后,细心的人可能会发现我打印的定时器延迟会有1-2ms的偏差,就是下面我要看的延迟时间可靠性的问题了
废话不多说直接上测试代码(为了测试效果这里我极端一点把延迟时间设置为1ms,这样数据结果会更加明显一点)
var time1 = new Date().getTime();
setInterval(function(){
var time2 = new Date().getTime();
console.log("setInterval执行的差值时间:"+(time2-time1));
},1);
常理上来讲运行的结果应该是1 2 3 4 5 6 8 9 ...
但是实际的运行结果如下:
可以看到平均的延迟时间大概在5ms左右,我是拿chrome浏览器测试的,据说不同的系统下不同的浏览器这个平均的时间都不一样,如果爱钻牛角尖的小伙伴可以去尝试一下,这里就不深入展开了.
所以说定时器的延迟时间不宜设置过小,因为太小的话可能根本也达不到你想要的效果(不排除一些真可以控制在1ms左右的牛逼浏览器),而且根据设备硬件或者浏览器的不同可能延迟时间也会有少量的误差
四、定时器的小妙用
有人会说定时器能有什么妙用,无外乎就是在手机翻转屏的时候延迟几秒获取设备宽度等等。
我想说的不是这些而是利用定时器来提高性能的办法。
首先我们来模拟一个js要动态创建十万个DOM节点的场景,这种情况浏览器会花费大量的时间来执行,从而阻塞了其他代码的执行。这时如果我们使用定时器把这十万个DOM打散分成多个部分,这样一下就好了很多。
五、合理管理定时器
大家都知道定时器,用完之后需要清除,如果不清楚同时多个定时器在一个页面上跑,会损耗性能让页面浏览起来有卡顿感,尤其是定时器在动画里面的应用,想象一下,如果制作一个动画效果里面用了许多定时器,并且多个定时器同时运行,那么有可能就会出现本来后面要执行的一个动画效果在前面就被提前执行了,这是我们不想看到的。
所以我们就要根据情况对定时器做一个合理的管理,还是拿做动画为例,
1.动画肯定要保持同一时间只执行一个定时器;
2.并且自己可以灵活的控制定时器的开启和关闭。
在网上可以找到管理定时器的示例代码,大家可以参考一下:
var timers = {
timerID : 0,
timers : [],
add : function (fn) {
this.timers.push(fn);
},
start : function (){
if(this.timerID) return;
(function runNext(){
if(timers.timers.length>0){
for(var i=0;i<timers.timers.length;i++){
if(timers.timers[i]() === false){
timers.timers.splice(i,1);
i--;
}
}
timers.timerID = setTimeout(runNext,10);
}
})();
},
stop : function(){
clearTimeout(this.timerID);
this.timerID = 0;
}
};
其实管理的核心还是我上面提到的两条。
总结
1.js中的定时器是在当前函数里,所有普通函数执行完成才开始执行定时器的;
2.定时器即使指定了时间,也不一定就能在指定的时间执行(只能比设置的时间长,不会比设置的时间短);
3.定时器如果设置的时间都相同,是按照定时器函数写的顺序来执行的,如果设置的时间不同,则是时间设置小的定时器函数会先执行,时间设置大的后执行;
4. setTimeout和setInterval在被触发的定义上是有很大区别的;
5.定时器的延迟时间不宜设置过小;
6.利用定时器可以分解大量操作的代码;
7.合理管理页面中的定时器。
感谢大家的阅读,有什么分析的不对的地方欢迎大家批评指出,如果喜欢本文,请点击右下角的推荐哦~