首页 > 技术文章 > 定时器深入讲解

ljwsyt 2018-09-06 10:05 原文

  定时器一般适用于需要定时刷新页面的地方,比如在页面做一个时钟。或者对于异步加载的情况下也可以使用,是一种思路,比如我需要给一个div设置样式或者内容,而此时该div并没有加载渲染,所有此时直接设置就会出现不生效的现象,因而可以使用定时器,延迟加载。当然,对于这种现象还有很多别的解决方法。

  定时器有timeoutinterval两种,其开启方法分别是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

事情的经过:

  1. 首先,第一个耗时任务(heavyTask())开始执行,它需要大约 1000ms 才能执行完毕。

  2. 从耗时任务开始执行,过了 100ms, 第一个 setTimeout 的回调函数期望执行,于是被加入到事件队列,但是此时前面的耗时任务还没执行完,所以它只能在队列中等待,直到耗时任务执行完毕它才开始执行,所以结果中我们开的看到的是: 第一个setTimeout回调执行等待时间: 1018

  3.第一个 setTimeout 回调一执行,又开启了第二个 setTimeout, 这个定时器也是期望延时 100ms 之后能够执行它的回调函数。 但是,在第一个 setTimeout 又存在一个耗时任务,所有它的剧情跟第一个定时器一样,也等待了 1000ms 才开始执行。

可以用下面的图来概括:

setTimeout

 

 

再来看 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 在准备把回调函数加入到事件队列的时候,会判断队列中是否还有未执行的回调,如果有的话,它就不会再往队列中添加回调函数。 不然,会出现多个回调同时执行的情况。

可以用下面的图来概括:
setInterval

 

想更深入理解原理可参考:

https://www.jianshu.com/p/61d42574bf0d

推荐阅读