揭秘 setInterval 不准的原因与解决方案

setInterval 是 JavaScript 中用于定时执行任务的常用方法。它的基本语法如下:

const intervalId = setInterval(callback, delay, ...args);
  • callback 是要执行的函数。
  • delay 是每次执行之间的时间间隔(以毫秒为单位)。
  • args 是传递给 callback 的附加参数。

但是,在实际使用中,可能会发现 setInterval 并不总是精确地按照预期的间隔时间来执行任务。这是因为 JavaScript 中的定时器并不是绝对精准的。

1. 为什么定时器不精准?

1.1 单线程执行

JavaScript 是单线程的,这意味着所有的任务都是在一个线程上排队执行的。虽然通过 setInterval 设置了定时器,想要周期性地执行某个任务,但如果在回调执行时主线程正在处理其他任务(比如执行计算、渲染 UI、处理用户事件等),就会导致回调函数被延迟执行,从而使定时器间隔时间不准确。

举个 🌰:设置了一个定时器,每隔 100ms 执行一次回调。

let count = 0;
const startTime = Date.now();
console.log(`Start time: ${startTime} ms`);

const intervalId = setInterval(() => {
  count++;
  console.log(`Callback executed: ${count}, Time: ${Date.now() - startTime} ms`);
}, 100);

// 这里模拟一个会占用主线程的长任务
setTimeout(() => {
  console.log(`Long task starting... Time: ${Date.now() - startTime} ms`);
  let start = Date.now();
  while (Date.now() - start < 500) {} // 占用 500ms 的时间
  console.log(`Long task finished, Time: ${Date.now() - startTime} ms`);
}, 0);

事件流和时间线分析:

t = 0ms:开始时刻,setInterval 在 100ms 后首次触发,然后执行 setTimeout 长任务。

t = 500ms:长任务结束,主线程被释放,但主线程的阻塞导致 setInterval 的回调没有按预定时间执行。

t = 100ms:setInterval 第一次回调,但由于主线程被阻塞,回调没有执行。

t = 600ms:setInterval 执行下一次回调。后面的 callback 也对应推迟。

输出日志:

主线程被占用时,定时器回调会被推迟,造成时间间隔的不准确。这种情况发生在主线程繁忙时,任务执行排队,定时器回调会“排队”到后面,等待后续执行。

1.2 任务排队和事件循环

JavaScript 使用事件循环来调度任务。setInterval 的回调是被放入 宏任务队列 中的,而宏任务队列的执行是有优先级的,只有在当前执行栈为空时,才会去执行队列中的任务。如果在执行回调时有其他更优先的任务(如 UI 渲染、用户输入处理等),那么定时器回调可能会被延迟。

举个 🌰

let count = 0;

const startTime = Date.now();
// 设置一个定时器,每 100ms 执行一次回调
const intervalId = setInterval(() => {
  count++;
  console.log(`setInterval callback executed: ${count}`);
}, 100);

// 模拟高优先级任务
console.log('Start of the script execution,time:', Date.now() - startTime);

// 使用 Promise 模拟一个高优先级任务
Promise.resolve().then(() => {
  console.log('Promise microtask started,time:', Date.now() - startTime);
  // 模拟一些耗时的同步操作
  let start = Date.now();
  while (Date.now() - start < 300) {} // 阻塞主线程 300ms
  console.log('Promise microtask finished,time:', Date.now() - startTime);
});

// 模拟低优先级的异步任务
setTimeout(() => {
  console.log('setTimeout task executed,time:', Date.now() - startTime);
}, 50);

console.log('End of the script execution,time:', Date.now() - startTime);

微队列优先执行,导致 setInterval 和 setTimeout 的回调被推迟。

1.3 回调执行时间的累积误差

setInterval 设定的间隔时间只是开始和结束之间的期望时间,但回调函数的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值