setTimeout和setInterval

342 阅读3分钟

介绍:

  • setTimeout延迟一段时间执行一次回调;用clearTimeout()终止

  • setInterval每隔一段时间执行回调;用clearInterval()终止

一直以为setTimeout是单位时间内执行代码,setInterval是单位时间重复执行代码。了解了一些eventloop和队列之后,才明白为什么这两个设置的单位时间会有不准的时候。他俩都是一样的在单位时间到达之后讲将代码推入任务队列(不是微任务队列)中。直到js主线程任务执行到它时才会执行代码。

let nowTime = new Date().getTime();
let count = 0; 
//耗时任务
for(let i=0;i<100000;i++){
    let a = i
};
setInterval(function(){
    count++;
    console.log("时间差:",new Date().getTime()-(nowTime + count*1000),"毫秒")
},1000)

image.png 只是一个简单的for循环就有了10毫秒+的误差,如果是更复杂或者更多的主线程序的话,误差会有更多,网上有一张很火的图解释了一个我认为更加夸张的误差,某种情况下会直接忽略掉某一个定时器......

image.png 上图可见每间隔100毫秒setInterval就会往队列中添加一个回调,100毫秒后添加T1,此时主线程序还有任务在执行,所以等待主线程空闲时执行T1代码;200毫秒后T2将回调加入任务队列,但是T1还在执行中,所以等待主线程序结束后执行T2代码,在300毫秒时加入t3,但是这时的T1代码刚结束,将要执行T2代码。但是在T3加入时主线程序中存在定时器实例。所以T3就被忽略掉了。

所以setInterval的缺点很明显:

  • 使用setInterval时,某种情况下会忽略某段回调
  • 可能多个定时器会连续执行(如上图所示T1执行结束后立即执行了T2,而并非 按照间隔执行之)

接下来看看setTimeout和for循环的经典问题

for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

结果是5个5,从打印的速度上看显然不是每秒一个5,而是一下全出来的5。究竟是什么导致的呢? for循环中的var声明出来的变量都是全局对象,当循环开始是i=0被存储到内存中,setTimeout在1s后将回调推入任务队列中,此时的主线程序还在执行for循环,所以setTimeout的回调都会在队列中排队,知道for执行结束,但因为i是全局对象,所以每次i改变后赋值操作访问的都是同一个内存地址。当任务队列开始执行打印时访问的i也是这个全局对象中的变量i,届时i的值只能是for循环最后一次的有效值5。所以上段代码是类似一秒输出5个5(因为队列也在排队执行,所以再快也会有误差)

个人小记,带我研究一下这两个的源码在更新一下。下次会带着settimeout模拟setinterval