调整setTimeout与setInterval的顺序为何会导致setInterval回调额外执行?代码执行次数差异原因解析
为什么调换setTimeout与setInterval的顺序会导致执行次数差异?
首先得澄清一个常见误区:JavaScript是单线程执行的,不存在所谓的“后台独立线程运行异步任务”——浏览器确实有单独的定时器线程负责计时,但定时器的回调函数必须等到主线程的调用栈清空后,才能从任务队列里被取出来执行。这也是两个代码执行次数不同的核心原因。
先看两个代码的具体情况:
代码1:先声明setTimeout,再声明setInterval
let isValid = true, counter = 0; setTimeout(() => { isValid = false; clearInterval(id); }, 2000); const id = setInterval(() => { if (isValid) { console.log(counter++); } }, 200);
它的执行流程是这样的:
- 主线程先跑完所有同步代码:声明变量,调用
setTimeout——浏览器定时器线程开始倒计时2000ms,到点后把回调扔进任务队列; - 接着调用
setInterval——定时器线程开始倒计时200ms,每到200ms就尝试把回调扔进任务队列(如果队列里没有该setInterval的待执行回调的话); - 主线程空闲后,开始逐个执行任务队列里的setInterval回调,每执行一次counter加1;
- 当2000ms到来时,setTimeout的回调被扔进任务队列。这时候关键的一点是:setTimeout的回调可能刚好在第9次setInterval回调执行完、第10次回调还没被扔进队列的时候,被主线程取出来执行了。于是
isValid被设为false,同时clearInterval终止了定时器,后续再也不会有setInterval回调被扔进队列。即使之后还有漏网的回调,执行时也会因为isValid为false而不打印,最终counter停在9。
代码2:先声明setInterval,再声明setTimeout
let isValid = true, counter = 0; const id = setInterval(() => { if (isValid) { console.log(counter++); } }, 200); setTimeout(() => { isValid = false; clearInterval(id); }, 2000);
差异就出在这里:
- 主线程先调用
setInterval,定时器线程立刻开始200ms的倒计时,第一个回调更早被扔进任务队列; - 之后才调用
setTimeout,启动2000ms的倒计时; - 当2000ms到来时,setTimeout的回调被扔进任务队列,但此时第10次setInterval的回调已经被定时器线程扔进任务队列了(因为setInterval的计时更早启动,刚好卡着2000ms的点把第10次回调送进去);
- 任务队列遵循“先到先得”的规则,主线程会先执行已经在队列里的第10次setInterval回调(此时
isValid还是true,所以会打印counter变成10),然后才执行setTimeout的回调去修改isValid和clearInterval。这就导致了多一次执行。
核心结论
- 定时器没有所谓的“执行优先级”,完全遵循任务队列的“先到先得”规则;
- 代码顺序影响了定时器的启动时机,进而影响了回调被扔进任务队列的先后顺序;
setInterval的逻辑是:每隔指定时间尝试把回调放进任务队列,如果队列里已有该定时器的待执行回调,就跳过这次(避免队列堆积)。所以当setTimeout的回调执行时机不同时,就会出现是否拦截到第10次回调的差异。
内容的提问来源于stack exchange,提问作者DecPK
相关产品推荐
相关产品推荐

