随着时间的推移, setTimeout 实际执行的时间和理想的时间差值会越来越大,这就不是我们预期的样子。
为什么 setTimeout 会不准时呢?
首先javascript是单线程的,这意味着javascript一次只能执行一个任务,而且所有任务都在一个线程上运行。当浏览器执行javascript代码时,它必须处理各种任务,包括处理用户输入、渲染页面、执行javascript代码等。
任务队列
为了处理不同类型的任务,浏览器引入了任务队列。任务队列分为两种主要类型:宏任务和微任务。setTimeout通常被认为是一个宏任务,promise.then()方法通常是微任务。
当setTimeout的延迟时间到达时,它会将指定的回调函数添加到宏任务队列,等待执行。但是,由于javascript是单线程的,只有在前面的任务完成后,宏任务队列中的任务才会被执行。这就可能导致setTimeout的延迟时间被延长。
竞争执行
另一个影响执行时间的因素是竞争执行。当浏览器中存在多个任务时,它们会根据优先级排队执行。例如,如果浏览器正在处理用户的点击事件、渲染页面以及执行javascript代码,setTimeout的回调函数必须等待前面的任务完成后才能执行。
最小延迟时间
另一个需要注意的是,setTimeout最小延迟时间并不是准确的时间,根据规范,setTimeout的最小延迟时间是4ms,这意味着即使你将延时设置为0ms,它仍然需要等待至少4ms才能执行。
总结来说,因为浏览器页面是有消息队列和事件循环来驱动的,创建一个 setTimeout 的时候是将它推进了一个队列,并没有立即执行,只有本轮宏任务执行完,才会去检查当前的消息队列是否有有到期的任务。
解决方法:
系统补偿定时器
?
当每一次定时器执行时后,都去获取系统的时间来进行修正,虽然每次运行可能会有误差,但是通过系统时间对每次运行的修复,能够让后面每一次时间都得到一个补偿。
// 系统补偿定时器 function timer(delay) { const startDate = new Date().getTime(); let count = 1; function instance() { const nowDate = new Date().getTime(); const realTime = nowDate - startDate; count++; const diff = delay * count - realTime; console.log({diff, realTime}); setTimeout(() => { instance(); }, diff); } setTimeout(() => { instance(); }, delay); } timer(500);