JS定时器的用法及工作原理

JS中有两个定时器,setTimeout和setInterval

setTimeout

定义

在指定的延迟时间之后调用一个函数或执行一个代码片段。

语法
let timeID = setTimeout(func,delay)

第一个参数为回调函数,第二个参数为延时的时间,setTimeout方法的返回值是一个数字,为该定时器的ID,这个ID是定时器的唯一标识,用clearTimeout(timeID)可以消除该定时器

setInterval

定义

周期性地调用一个函数(function)或者执行一段代码。
语法和setTimeout一样

关于第二个参数delay

由于javascript 的事件循环机制,导致第二个参数并不代表延迟delay毫秒之后立即执行回调函数,而是尝试将回调函数加入到事件队列。实际上,setTimeout 和 setInterval 在这一点上处理又存在区别:

  • setTimeout:延时delay毫秒之后,啥也不管,直接将回调函数加入事件队列。
  • setInterval: 延时delay毫秒之后,先看看事件队列中是否存在还没有执行的回调函数(setInterval的回调函数),如果存在,就不要再往事件队列里加入回调函数了。

所以,当我们的代码中存在耗时的任务时,定时器并不会表现的如我们所想的那样。

举一个栗子
function now(){
    return new Date()
}
var start = now()
setTimeout(function(){
    console.log('第一个setTimeout回调执行等待时间:',now() - start)

    var start2 = now()
    setTimeout(function(){
         console.log('第二个setTimeout回调执行等待时间:',now() - start2) 
    },100)
}, 100)

得到

栗子1

但是如果加上耗时的任务,结果就跟我们想的不太一样了

第二个栗子
var timerStart1 = new Date()
setTimeout(function () {
  console.log('第一个setTimeout回调执行等待时间:', new Date()- timerStart1);

  var timerStart2 = new Date();
  setTimeout(function () {
    console.log('第二个setTimeout回调执行等待时间:', new Date() - timerStart2);
  }, 100)

  heavyTask()

}, 100);

var loopStart = new Date()
heavyTask()
console.log('heavyTask耗费时间:', new Date() - loopStart, 'ms')

function heavyTask(){
    for(var i = 0; i<1000; i++)
    {
        console.log(1)
    }
}
JS定时器的用法及工作原理_第1张图片
第二个栗子结果

可以看到在heavytask完成之后,还没有等到100ms,第一个setTimeout就立刻执行了,【即预想中第一个setTimeout的等待时间应该是372+100,而不是373】,同样的第二个setTimeout也没有等heavyTask完成后的100ms再去执行

这就是因为

setTimeout:延时delay毫秒之后,啥也不管,直接将回调函数加入事件队列。

我们来一步步说下事情的经过
1.首先,heavyTask开始执行,它 完成需要372ms.
2.从耗时任务开始执行,第一个定时器等待了100ms后,就想把自己加入事件队列,但是此时前面的耗时任务还没执行完,所以它只能在队列中等待,直到耗时任务执行完毕它才开始执行,所以结果中我们开的看到的是: 第一个setTimeout回调执行等待时间: 373。
3.同理,第二个定时器在等待了100ms之后就急不可耐地想要执行,把自己加入到事件队列中,等到耗时任务执行完毕后,它立马就执行

JS定时器的用法及工作原理_第2张图片
第二个栗子图解

注意,之前栗子中的第二个定时器是包裹在第一个定时器内部的,
当定时器都是相互独立时,他们加入队列的顺序由delay延迟时间来决定

第三个栗子
setTimeout(function() {
  console.log('延迟700ms')
}, 700);
setTimeout(function() {
  console.log('延迟100ms')
}, 100);
setTimeout(function() {
  console.log('延迟200ms')
}, 200);

var loopStart = new Date()
heavyTask()
console.log('heavyTask耗费时间:', new Date() - loopStart, 'ms')

function heavyTask(){
    for(var i = 0; i<5000; i++)
    {
        console.log(1)
    }
}

JS定时器的用法及工作原理_第3张图片
第三个栗子

延迟100ms,延迟200ms,延迟700ms是同时输出的。

setInterval的栗子
var intervalStart = now();
setInterval(function () {
  console.log('interval距定义定时器的时间:', now() - loopStart);
}, 100);

var loopStart = now();
heavyTask();
console.log('heavyTask耗费时间:', now() - loopStart);

function heavyTask() {
  var s = now();
  while(now() - s < 1000) {
  }
}

function now () {
  return new Date();
}
// 输出:
// heavyTask耗费时间: 1013
// interval距定义定时器的时间: 1016
// interval距定义定时器的时间: 1123
// interval距定义定时器的时间: 1224

上面这段代码,每隔 100ms 就打出一条日志。相对于 setTimeout 的区别, setInterval 在准备把回调函数加入到事件队列的时候,会判断队列中是否还有未执行的回调,如果有的话,它就不会再往队列中添加回调函数。 不然,会出现多个回调同时执行的情况。

JS定时器的用法及工作原理_第4张图片
第四个栗子
关于setTimeout(function,0)

我们已经知道setTimeout会等到其他事件执行之后,再执行自己的回调函数,如果第二个参数为0,也需要等待其他所有的事件执行之后才能执行自己的函数

var a = 1;
setTimeout(function(){
    a = 2;
    console.log(a);
}, 0);
var a ;
console.log(a);
a = 3;
console.log(a);

以上代码会输出1 3 2 setTimeout(function,0)会放在执行队列最后,当其他事件执行之后才开始执行

参考
https://segmentfault.com/a/1190000003764106

你可能感兴趣的:(JS定时器的用法及工作原理)