以下为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()
方法中清空这个计时器。