setTimeout与setInterval的区别

lxf2023-05-10 11:11:01

setTimeout与setInterval的区别

setTimeout与setInterval有什么区别,这是我6年前面试腾讯的一道面试题,上面是ChatGPT的回答。简单来说,setTimeout是一次性定时器,setInterval是周期性定时器,如果你的回答也停留在api的字面解释,那chatgpt很可能会取代你的工作。递归地调用setTimeout,也能像setInterval一样实现周期性定时器,如下:

// start函数中调用了setTimeout,会在100ms后递归调用start,实现周期性定时器
let index = 1
const start = () => setTimeout(() => {
    // 终止条件,最多调用5次
    if(index++ >= 5) {            
        return
    }       
    // 递归调用
    start()
}, 100)
start()

为了更直观在性能看板观察运行情况,增加了两个逻辑,调用delay函数拉长定时任务执行时长,并调用performance.mark和performance.measure标记间隔时长

let index = 1
const delay = () => {
    const now = Date.now()
    while(Date.now() - now < 200);
}
const start = () => {
    setTimeout(() => {
        // 为了方便在性能看板观察间隔时长
        performance.measure(`setTimeout间隔${index}`, `setTimeout间隔${index}`)
        
        // 耗时操作200ms
        delay()   
        
        if(index++ >= 5) {            
            return
        }
         
       
        performance.mark(`setTimeout间隔${index}`)  
        // 递归调用
        start()
    }, 100)
}
performance.mark(`setTimeout间隔${index}`)
start()

setTimeout与setInterval的区别 通过面板发现,定时任务的间隔时长是相等的,但是一个周期的总耗时是300ms,也就是执行耗时 + 间隔耗时,这没什么特别的,我们再使用setInterval实现相同的逻辑。

let index = 1
const delay = () => {
    const now = Date.now()
    while(Date.now() - now < 200);
}
const start = () => {
    const ticker = setInterval(() => {
        // 为了方便在性能看板观察间隔时长
        performance.measure(`setTimeout间隔${index}`, `setTimeout间隔${index}`)
        
        // 耗时操作200ms
        delay() 
        
        if(index++ >= 5) {            
            clearInterval(ticker)
            return
        }
       
        performance.mark(`setTimeout间隔${index}`)
    }, 100)
}
performance.mark(`setTimeout间隔${index}`)
start()

setTimeout与setInterval的区别 发现除了第一个间隔是100ms,后面其他间隔的耗时都可以忽略不计,定时器出现一个连续执行的现象,每一个周期的总耗时是200ms,也就是Math.max(执行耗时, 间隔耗时),当执行耗时大于间隔耗时,间隔失效连续执行。

js在单线程环境中执行,定时任务在指定时间加入事件队列,等待主线程空闲时,事件队列中的任务再加入执行栈执行。setInterval回调函数加入事件队列的时间点是固定的,当队列中存在重复的定时任务会进行丢弃。比如上面的例子,理论上每100ms会往事件队列中加入定时任务,由于每个周期主线程执行耗时是200ms,期间可以加入两个定时任务,由于第二个定时任务加入时,第一个定时任务还在事件队列中,重复的定时任务会被丢弃,200ms后主线程空闲,事件队列中只有一个定时任务,会立刻加入执行栈由主线程执行,由于定时任务的执行耗时大于间隔耗时,每次主线程执行完定时任务,事件队列中总会有一个新的任务在等待,所以出现了连续执行。而setTimeout的定时任务依赖上一次定时任务执行结束再调用定时器,所以定时任务之间的间隔是固定的,但是整个定时任务的周期会大于设置的间隔时长。

小结

setInterval加入事件队列的时间是固定的,setTimeout加入事件队列的时间是执行耗时 + 间隔耗时。 setInterval任务间的间隔是 Math.max(执行耗时, 间隔耗时),setTimeout任务间的间隔是固定的。

这两个特性在实际开发中有什么影响吗?

轮询场景:当我们需要轮询查询某一个接口时,比如支付成功后查询订单的支付状态,为了提升性能,最好根据返回结果判断是否触发下一次查询,如果订单状态更新了,停止发送查询请求,避免不必要的开销。这个场景使用setTimeout更适合,因为它可以根据请求返回结果判断是否触发新的定时任务,而setInterval会在固定的间隔去触发请求,某一次查询请求的响应时长大于定时器间隔时长,将会发送多余的请求。

动画场景:比如像倒计时,使用setInterval会比setTimeout更稳定,因为定时任务的间隔更接近设置的间隔。当然实现动画用requestAnimationFrame性能更佳,两者的性能差异可通过文章了解。

最后,2023年找工作太难了,如果有合适的前端机会欢迎跟我联系,目前base深圳,微信号:jsconference。