setTimeout与setInterval实现

手写实现浏览器定时器API setTimeout和setInterval的功能

问题

在JavaScript中,setTimeoutsetInterval 是常用的定时器API。这道题要求我们手动实现这两个函数的功能,包括:

  • 延迟执行回调函数
  • 支持传递参数
  • 返回定时器ID用于清除
  • 实现clearTimeout和clearInterval

解答

// 定时器管理类
class TimerManager {
  constructor() {
    this.timerId = 0; // 定时器ID计数器
    this.timers = new Map(); // 存储所有定时器
  }

  /**
   * 实现setTimeout
   * @param {Function} callback - 回调函数
   * @param {number} delay - 延迟时间(毫秒)
   * @param {...any} args - 传递给回调函数的参数
   * @returns {number} 定时器ID
   */
  setTimeout(callback, delay = 0, ...args) {
    const id = this.timerId++;
    
    const timer = {
      id,
      type: 'timeout',
      startTime: Date.now(),
      callback,
      delay,
      args
    };

    // 使用原生setTimeout实现
    const timeoutId = globalThis.setTimeout(() => {
      // 执行回调
      callback(...args);
      // 执行完后删除定时器记录
      this.timers.delete(id);
    }, delay);

    timer.nativeId = timeoutId;
    this.timers.set(id, timer);

    return id;
  }

  /**
   * 实现setInterval
   * @param {Function} callback - 回调函数
   * @param {number} interval - 间隔时间(毫秒)
   * @param {...any} args - 传递给回调函数的参数
   * @returns {number} 定时器ID
   */
  setInterval(callback, interval = 0, ...args) {
    const id = this.timerId++;
    
    const timer = {
      id,
      type: 'interval',
      startTime: Date.now(),
      callback,
      interval,
      args
    };

    // 使用原生setInterval实现
    const intervalId = globalThis.setInterval(() => {
      callback(...args);
    }, interval);

    timer.nativeId = intervalId;
    this.timers.set(id, timer);

    return id;
  }

  /**
   * 清除setTimeout
   * @param {number} id - 定时器ID
   */
  clearTimeout(id) {
    const timer = this.timers.get(id);
    if (timer && timer.type === 'timeout') {
      globalThis.clearTimeout(timer.nativeId);
      this.timers.delete(id);
    }
  }

  /**
   * 清除setInterval
   * @param {number} id - 定时器ID
   */
  clearInterval(id) {
    const timer = this.timers.get(id);
    if (timer && timer.type === 'interval') {
      globalThis.clearInterval(timer.nativeId);
      this.timers.delete(id);
    }
  }

  /**
   * 清除所有定时器
   */
  clearAll() {
    this.timers.forEach(timer => {
      if (timer.type === 'timeout') {
        globalThis.clearTimeout(timer.nativeId);
      } else {
        globalThis.clearInterval(timer.nativeId);
      }
    });
    this.timers.clear();
  }
}

// 创建全局实例
const timerManager = new TimerManager();

// 导出API
const mySetTimeout = timerManager.setTimeout.bind(timerManager);
const mySetInterval = timerManager.setInterval.bind(timerManager);
const myClearTimeout = timerManager.clearTimeout.bind(timerManager);
const myClearInterval = timerManager.clearInterval.bind(timerManager);

使用示例

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

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

// 示例3: 清除setTimeout
const id = mySetTimeout(() => {
  console.log('这条不会执行');
}, 2000);
myClearTimeout(id);

// 示例4: 基本的setInterval使用
let count = 0;
const intervalId = mySetInterval(() => {
  count++;
  console.log(`执行次数: ${count}`);
  
  // 执行5次后清除
  if (count >= 5) {
    myClearInterval(intervalId);
    console.log('定时器已清除');
  }
}, 500);

// 示例5: setInterval传递参数
const countdownId = mySetInterval((from) => {
  console.log(`倒计时: ${from}`);
  from--;
  if (from < 0) {
    myClearInterval(countdownId);
  }
}, 1000, 5);

// 示例6: 清除所有定时器
mySetTimeout(() => console.log('定时器1'), 3000);
mySetTimeout(() => console.log('定时器2'), 4000);
mySetInterval(() => console.log('循环定时器'), 1000);

// 2秒后清除所有定时器
setTimeout(() => {
  timerManager.clearAll();
  console.log('所有定时器已清除');
}, 2000);

关键点

  • 定时器ID管理:使用递增的ID来唯一标识每个定时器,便于后续清除操作

  • Map数据结构:使用Map存储定时器信息,支持快速查找和删除操作

  • 参数传递:使用剩余参数(…args)和展开运算符支持向回调函数传递任意数量的参数

  • 类型区分:通过type字段区分timeout和interval类型,确保清除时调用正确的原生API

  • 自动清理:setTimeout执行完毕后自动从Map中删除,避免内存泄漏

  • 绑定this:导出API时使用bind绑定timerManager实例,确保方法内this指向正确

  • 扩展性:通过TimerManager类封装,便于添加更多功能(如暂停、恢复、查询等)

  • 原生API封装:基于原生setTimeout/setInterval实现,保证了时间精度和性能