使用 setInterval 模拟实现 setTimeout

通过 setInterval 的循环特性来模拟 setTimeout 的延迟执行功能,理解定时器的底层原理

问题

setTimeout 用于在指定延迟后执行一次回调函数,而 setInterval 会周期性地重复执行。本题要求使用 setInterval 来模拟实现 setTimeout 的功能,即:在指定时间后执行一次回调,并且能够被清除。

这道题考察了对 JavaScript 定时器机制的理解,以及如何通过循环定时器实现单次执行的逻辑。

解答

/**
 * 使用 setInterval 模拟实现 setTimeout
 * @param {Function} callback - 延迟执行的回调函数
 * @param {number} delay - 延迟时间(毫秒)
 * @param {...any} args - 传递给回调函数的参数
 * @returns {number} 定时器 ID,用于清除定时器
 */
function mySetTimeout(callback, delay, ...args) {
  // 使用 setInterval 创建一个循环定时器
  const timer = setInterval(() => {
    // 执行回调函数,传入参数
    callback(...args);
    
    // 执行完毕后立即清除定时器,确保只执行一次
    clearInterval(timer);
  }, delay);
  
  // 返回定时器 ID,用于外部清除
  return timer;
}

/**
 * 清除通过 mySetTimeout 创建的定时器
 * @param {number} timerId - 定时器 ID
 */
function myClearTimeout(timerId) {
  clearInterval(timerId);
}

使用示例

// 示例 1:基本使用
console.log('开始执行');
mySetTimeout(() => {
  console.log('2秒后执行');
}, 2000);

// 示例 2:传递参数
mySetTimeout((name, age) => {
  console.log(`姓名:${name},年龄:${age}`);
}, 1000, '张三', 25);

// 示例 3:清除定时器
const timerId = mySetTimeout(() => {
  console.log('这段代码不会执行');
}, 3000);

// 在定时器触发前清除
myClearTimeout(timerId);

// 示例 4:链式调用模拟
mySetTimeout(() => {
  console.log('第一步');
  mySetTimeout(() => {
    console.log('第二步');
    mySetTimeout(() => {
      console.log('第三步');
    }, 1000);
  }, 1000);
}, 1000);

// 示例 5:与原生 setTimeout 对比
console.log('--- 对比测试 ---');
const start = Date.now();

mySetTimeout(() => {
  console.log(`mySetTimeout 执行时间: ${Date.now() - start}ms`);
}, 1000);

setTimeout(() => {
  console.log(`setTimeout 执行时间: ${Date.now() - start}ms`);
}, 1000);

关键点

  • 思路:利用 setInterval 创建定时器,在回调函数执行后立即调用 clearInterval 清除定时器,从而实现只执行一次的效果

  • 参数传递:使用剩余参数 ...args 收集额外参数,并通过展开运算符传递给回调函数,保持与原生 setTimeout 的参数传递方式一致

  • 返回值处理:返回 setInterval 的定时器 ID,使得外部可以通过 clearInterval 提前清除定时器

  • 清除机制:提供 myClearTimeout 函数,本质上调用 clearInterval,保持 API 的一致性

  • 执行时机setIntervalsetTimeout 的执行时机基本一致,都是在指定延迟后将回调函数加入事件队列

  • 注意事项:虽然功能相似,但 setInterval 的精度可能略有不同,在高精度场景下需要注意误差