为什么要用 setTimeout 模拟 setInterval ?
#2023/03/20 #前端
目录
1. 先说说 setInterval 的问题
setInterval(fn, N);
- 即
fn()将会在N秒之后被推入任务队列 - 但每次推之前,都要判断看
上次的任务是否还在队列中,- 如果在,则不添加。所以
setInterval有两个缺点:- 使用
setInterval时,某些间隔会被跳过; - 甚至可能多个定时器会
连续执行,即刚好在两个队列的缝隙时,会连续执行
- 使用
- 如果在,则不添加。所以
2. 再看看 setTimeout
一个经典案例:
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
为什么是一秒后输出了 5 个 5 呢?
for是主线程代码,先执行完了,才轮到执行setTimeout- 每个
setTimeout产生的新的任务会直接push到任务队列中。
而且它是一次性的,或者换个思路,setInterval 循环执行,链路长,不好控制,而 setTimeout 只是延时一次 ,方便控制。
3. 使用 setTimeout 来模拟 setInterval
function mySetInterval(fn, timeout) {
// 关键,标识是否继续,并返回
let timer = {
flag: true
}
// 两次 settimeout,需要闭包定义一个函数
function func() {
if (timer.flag) {
fn();
setTimeout(func, timeout);
}
}
setTimeout(func, timeout);
return timer;
}
// 测试
const timer = mySetInterval(() => {
console.log('log 1');
},1000)
// 5s后,停止定时器
setTimeout(() => {
timer.flag = false;
},5000)
[!danger] 手写这种代码时,
套路就是 函数 里 再定义一个函数,形成闭包,另外需要返回 定时标识,另外一定会有递归,比如这里的func
4. 最后
再次强调,定时器指定的时间间隔,表示的是 何时将定时器的代码添加到消息队列,而不是何时执行代码。 所以真正何时执行代码的时间是不能保证的,取决于何时被主线程的事件循环取到,并执行.
W3C在HTML标准中规定,规定要求 setTimeout 中低于4ms的时间间隔算为4ms。