以下为ChatGPT自动生成的解答,正确与否请自行判断。

BFE.devAI解答 - JavaScript 代码题
36. 实现一个 fake timer(setTimeout)

一个可能的实现如下:

class FakeTimer {
  constructor() {
    this.timers = new Map(); // 存储 timer 的 id 和 callback
    this.currentTime = 0; // 初始时间戳为 0
    this.originalDateNow = Date.now.bind(Date); // 保存原始的 Date.now 方法
  }

  install() {
    const self = this;
    const fakeDateNow = () => self.currentTime; // 实现一个 fake 的 Date.now 方法
    Date.now = fakeDateNow;

    const fakeSetTimeout = (fn, delay, ...args) => {
      const id = Symbol(); // 生成一个唯一的id
      const callback = () => {
        fn(...args); // 执行回调
        self.timers.delete(id); // 删除设置的timer
        self.tick(); // 继续往下执行,直到没有timer或到达下一个timer的时间
      };
      self.timers.set(id, callback); // 保存id和callback
      return id;
    };
    window.setTimeout = fakeSetTimeout; // 在全局覆盖 setTimeout

    const fakeClearTimeout = id => {
      self.timers.delete(id);
    };
    window.clearTimeout = fakeClearTimeout; // 在全局覆盖 clearTimeout
  }

  uninstall() {
    Date.now = this.originalDateNow; // 恢复原始的 Date.now 方法
    window.setTimeout = undefined; // 恢复原始的 setTimeout 方法
    window.clearTimeout = undefined; // 恢复原始的 clearTimeout 方法
    this.timers.clear();
  }

  tick() {
    while (this.timers.size > 0) {
      const nextTime = Math.min(...this.timers.values().map(callback => {
        return callback._runAt || Infinity; // 获取最近的 timer 执行时间
      }));
      this.currentTime = nextTime; // 跳转到下一个最近的时间
      for (const [id, callback] of this.timers.entries()) {
        if (callback._runAt === nextTime) {
          callback(); // 执行回调
        }
      }
    }
  }
}

实现思路:

我们首先需要一个 timers map,用于存储当前设置的 timer,并且唯一标识它们的 id 与其回调函数。然后我们需要一个 currentTime 变量,用于记录当前时间戳,初始值为 0。我们还需要一个 Date.now() 的 fake 方法,用于提供新的时间戳。

install() 方法中,我们覆盖了原始的 Date.now() 方法和 setTimeout() 方法,并且通过 Symbol 生成了唯一的 id,并且将 id 和 callback 存储在 timers map 中。覆盖函数实现时,我们额外传递了一个 ...args,这是为了能够支持在 setTimeout() 中传入参数。

tick() 方法中,我们循环执行 timers map 中所有的回调函数,只要它们的 _runAt 属性等于 current time(即从当前时间到下一个最近的 timer 的时间内所有设置的 timer)或者它们没有设置 _runAt(即第一次执行)。

最后在 uninstall() 方法中,我们恢复了原始的 Date.now() 方法和 setTimeout() 方法,并且清空 timers map。

完整的测试:

const fakeTimer = new FakeTimer();
fakeTimer.install();

const logs = [];
const log = (arg) => {
  logs.push([Date.now(), arg]);
};

setTimeout(() => log('A'), 100); // 在时间戳 100 上执行 callback A
const b = setTimeout(() => log('B'), 110);
clearTimeout(b); // 直接删除了 b

setTimeout(() => log('C'), 200); // 在时间戳 200 上执行 callback C

fakeTimer.tick(); // 执行所有设置的 timer

fakeTimer.uninstall();

expect(logs).toEqual([[100, 'A'], [200, 'C']]);

其中,我们通过 fakeTimer.tick() 执行所有设置的 timer。由于我们的实现中没有真正的 timer,因此所有的 timer 都将在同一时间点被执行,如果要模拟真实世界,我们可能需要在 fakeSetTimeout() 方法中模拟一个计时器(比如使用 setInterval),然后在 fakeClearTimeout() 方法中清空这个计时器。