定时器一般适用于需要定时刷新页面的地方,比如在页面做一个时钟。或者对于异步加载的情况下也可以使用,是一种思路,比如我需要给一个div设置样式或者内容,而此时该div并没有加载渲染,所有此时直接设置就会出现不生效的现象,因而可以使用定时器,延迟加载。当然,对于这种现象还有很多别的解决方法。
定时器有timeout和interval两种,其开启方法分别是setTimeout()和setInterval(),参数有两个,第一个是要执行的回调方法函数,第二个是时间delay,时间是毫秒单位;清除方法是clearTimeout,clearInterval,参数是具体的定时器。
一般的使用是(timeout和interval类似,以下只着重讲interval),
var a = setInterval(function(){ clearInterval(a); }, 1000);
IE0+ 还支持回调参数的传入:
var timeoutID = window.setTimeout(function(){}, delay, [param1, param2, ...]); //setTimeout和setInterval都是window对象的方法,所有平常使用并不比写window.
两者的区别:timeout只执行一次,所以也可以不必刻意清除,而interval则是每隔设置的时间就执行一次,所有如果使用较多而又不清除,就会使页面占用较大的内存。
有时候,业务场景需要我们开启多个不定个的定时器,而这个时候清楚定时器就不再那么直接。比如,在页面上展现N个用户的信息,且要定时刷新,且这N个用户信息刷新的接口或者方法又不同,就需要使用不同的定时器。我也曾用定时器写过一个页面小游戏,就是类似接彩色豆子那种。
这个时候,如果刷新完了要批量清理定时器,有两个方法:
(1)强制暴力清除:
原理:定时器在一些低版本谷歌和火狐上是从1开始的一个number(比如上边的a,控制台输入a回车后,可以看到a其实就是一个数字),当前页面有几个定时器,a就是几,而高版本的谷歌和ie上,定时器就是一个随机的number了。因而我们可以设置一个很大的数字,然后依次清除(clearInterval(具体数字)):
1 for(var i = 1; i < 10000; i++) { 2 clearInterval(i); 3 }
但是这种方法很不推荐,我也曾使用过,非常不好用而已要执行很多次。
(2)使用数组或者对象来存储定时器,然后遍历清除:
使用对象:
var pageTimer = {} ; //定义计算器全局变量 //赋值模拟 pageTimer["timer1"] = setInterval(function(){},2000); pageTimer["timer2"] = setInterval(function(){},2000); ... //全部清除方法 for(var each in pageTimer){ clearInterval(pageTimer[each]); }
使用数组:
var pageTimer = [] ; //定义计算器全局变量 //赋值模拟 pageTimer.push(setInterval(function(){},2000)); pageTimer.push(setInterval(function(){},2000)); ... //全部清除方法 $.each(pageTimer, function(index, e){ clearInterval(e); })
一般使用方法二,而不使用暴力清除。
定时器的原理:
javascript 的事件循环机制,导致第二个参数并不代表延迟delay毫秒之后立即执行回调函数,而是尝试将回调函数加入到事件队列。实际上,setTimeout
和 setInterval
在这一点上处理又存在区别:
-
setTimeout:延时delay毫秒之后,直接将回调函数加入事件队列。
-
setInterval: 延时delay毫秒之后,先看事件队列中是否存在还没有执行的回调函数(setInterval的回调函数),如果存在,就不要再往事件队列里加入回调函数了。
所以,当我们的代码中存在耗时的任务时,定时器并不会表现的如我们所想的那样。
举例:
1 function now() { 2 return new Date(); 3 } 4 var time1 = now(); 5 setTimeout(function () { 6 console.log('第一个timeout回调执行等待时间:', now() - time1); 7 8 var time2 = now(); 9 setTimeout(function () { 10 console.log('第二个timeout回调执行等待时间:', now() - time2); 11 }, 100); 12 }, 100); 13 14 //控制台打印 15 //第一个timeout回调执行等待时间: 103 16 //第二个timeout回调执行等待时间: 100
可以看到时间间隔并不一定等于设置的delay 100毫秒。
在此基础上:
var timerStart1 = now(); setTimeout(function () { console.log('第一个setTimeout回调执行等待时间:', now() - timerStart1); var timerStart2 = now(); setTimeout(function () { console.log('第二个setTimeout回调执行等待时间:', now() - timerStart2); }, 100); heavyTask(); // 耗时任务 }, 100); var loopStart = now(); heavyTask(); // 耗时任务 console.log('heavyTask耗费时间:', now() - loopStart); function heavyTask() { var s = now(); while(now() - s < 1000) { } } //控制台输出 //heavyTask耗费时间: 1000 //undefined //VM424:3 第一个setTimeout回调执行等待时间: 1001 //VM424:7 第二个setTimeout回调执行等待时间: 1002
事情的经过:
-
首先,第一个耗时任务(
heavyTask()
)开始执行,它需要大约1000ms
才能执行完毕。 -
从耗时任务开始执行,过了
100ms
, 第一个setTimeout
的回调函数期望执行,于是被加入到事件队列,但是此时前面的耗时任务还没执行完,所以它只能在队列中等待,直到耗时任务执行完毕它才开始执行,所以结果中我们开的看到的是:第一个setTimeout回调执行等待时间: 1018
。
3.第一个 setTimeout
回调一执行,又开启了第二个 setTimeout
, 这个定时器也是期望延时 100ms
之后能够执行它的回调函数。 但是,在第一个 setTimeout
又存在一个耗时任务,所有它的剧情跟第一个定时器一样,也等待了 1000ms 才开始执行。
可以用下面的图来概括:
再来看 setInterval
的一个例子:
1 var intervalStart = now(); 2 setInterval(function () { 3 console.log('interval距定义定时器的时间:', now() - loopStart); 4 }, 100); 5 6 var loopStart = now(); 7 heavyTask(); 8 console.log('heavyTask耗费时间:', now() - loopStart); 9 10 function heavyTask() { 11 var s = now(); 12 while(now() - s < 1000) { 13 } 14 }
控制台打印:
heavyTask耗费时间: 1000
undefined
VM426:3 interval距定义定时器的时间: 1000
VM426:3 interval距定义定时器的时间: 1100
VM426:3 interval距定义定时器的时间: 1201
VM426:3 interval距定义定时器的时间: 1300
VM426:3 interval距定义定时器的时间: 1402
VM426:3 interval距定义定时器的时间: 1501
...
上面这段代码,我们期望每隔 100ms
就打出一条日志。相对于 setTimeout
的区别, setInterval
在准备把回调函数加入到事件队列的时候,会判断队列中是否还有未执行的回调,如果有的话,它就不会再往队列中添加回调函数。 不然,会出现多个回调同时执行的情况。
可以用下面的图来概括:
想更深入理解原理可参考:
https://www.jianshu.com/p/61d42574bf0d