注册
web

setTimeout与setInterval的区别

13771677231027_.pic.jpg


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()
复制代码

image.png
通过面板发现,定时任务的间隔时长是相等的,但是一个周期的总耗时是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()
复制代码

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


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


小结


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


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


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


动画场景:比如像倒计时,使用setInterval会比setTimeout更稳定,因为定时任务的间隔更接近设置的间隔。当然实现动画用requestAnimationFrame性能更佳。


作者:学前端得永生
链接:https://juejin.cn/post/7203714680316444732
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册