用 setTimeout 实现 setInterval

使用 setTimeout 递归调用来模拟 setInterval 的效果

问题

如何使用 setTimeout 实现 setInterval 的功能?

解答

setInterval 的问题

setInterval 每隔一段时间将回调函数加入事件队列,而不是立即执行。当执行栈中的任务耗时过长时,事件队列中会积累多个定时器事件,导致这些事件在执行栈清空后连续执行,无法保证固定的时间间隔。

使用 setTimeout 模拟

通过 setTimeout 递归调用可以解决这个问题,确保上一个任务执行完成后才触发下一个定时器。

function mySetInterval(fn, timeout) {
  // 控制器,用于停止定时器
  var timer = {
    flag: true
  };

  // 递归函数,模拟 setInterval
  function interval() {
    if (timer.flag) {
      fn();
      setTimeout(interval, timeout);
    }
  }

  // 启动定时器
  setTimeout(interval, timeout);

  // 返回控制器,用于清除定时器
  return timer;
}

// 使用示例
var timer = mySetInterval(() => {
  console.log('执行任务');
}, 1000);

// 停止定时器
// timer.flag = false;

清除定时器的实现

function myClearInterval(timer) {
  timer.flag = false;
}

// 使用
var timer = mySetInterval(() => {
  console.log('执行任务');
}, 1000);

// 3秒后停止
setTimeout(() => {
  myClearInterval(timer);
}, 3000);

关键点

  • setInterval 会在事件队列中积累多个回调,可能导致连续执行
  • setTimeout 递归调用确保前一个任务完成后才执行下一个
  • 使用对象的 flag 属性控制递归的继续或停止
  • 每次递归都创建新的 setTimeout,避免事件堆积