setTimeout 模拟 setInterval
用 setTimeout 递归实现 setInterval 的功能
问题
使用 setTimeout 模拟 setInterval 的功能。
解答
基础实现
function mySetInterval(fn, delay) {
let timerId = null;
function loop() {
fn();
// 执行完成后再设置下一次定时
timerId = setTimeout(loop, delay);
}
// 启动第一次定时
timerId = setTimeout(loop, delay);
// 返回取消方法
return {
clear() {
clearTimeout(timerId);
}
};
}
// 使用示例
const timer = mySetInterval(() => {
console.log('执行时间:', new Date().toLocaleTimeString());
}, 1000);
// 5 秒后停止
setTimeout(() => {
timer.clear();
console.log('已停止');
}, 5000);
支持传参的完整版本
function mySetInterval(fn, delay, ...args) {
let timerId = null;
let isRunning = true;
function loop() {
if (!isRunning) return;
fn.apply(this, args);
timerId = setTimeout(loop, delay);
}
timerId = setTimeout(loop, delay);
// 返回一个类似 timerId 的对象
return {
clear() {
isRunning = false;
clearTimeout(timerId);
}
};
}
// 使用示例
const timer = mySetInterval((name) => {
console.log(`Hello, ${name}!`);
}, 1000, 'World');
为什么要用 setTimeout 模拟?
setInterval 存在任务堆积问题:
// setInterval 的问题
let count = 0;
setInterval(() => {
count++;
console.log(`第 ${count} 次执行`);
// 模拟耗时操作(超过间隔时间)
const start = Date.now();
while (Date.now() - start < 2000) {}
}, 1000);
// 任务会堆积,因为 setInterval 不管上一次是否执行完
// setTimeout 模拟可以避免堆积
function safeInterval(fn, delay) {
let timerId = null;
function loop() {
fn(); // 等 fn 执行完
timerId = setTimeout(loop, delay); // 才设置下一次
}
timerId = setTimeout(loop, delay);
return {
clear: () => clearTimeout(timerId)
};
}
关键点
setTimeout递归调用自身实现循环- 在回调执行完成后再设置下一次定时,避免任务堆积
- 需要保存
timerId以支持取消功能 setInterval不等待回调完成就计时,可能导致回调堆积- 实际项目中推荐用
setTimeout模拟,执行时机更可控
目录