一、setInterval存在的问题
直接例子:
let startTime = new Date().getTime();
let count = 0;
setInterval(function () {
count++;
console.log(
`与原设定的间隔时差了${
new Date().getTime() - (startTime + count * 1000)
}毫秒`
);
}, 1000);
在执行中可能会造成一点时间误差,但是可以忽略不计。
然而,我们把代码改一改:
let startTime = new Date().getTime();
let count = 0;
//耗时任务
setInterval(function () {
let i = 0;
while (i++ < 1000000000);
}, 0);
setInterval(function () {
count++;
console.log(
`与原设定的间隔时差了${
new Date().getTime() - (startTime + count * 1000)
}
毫秒`
);
}, 1000);
我们可以看到,相差的毫秒数变得非常大并且还在不停的增加,这个误差是不可接受的。
那是什么原因造成了这个问题呢?
原因⚠️:是在EventLoop
中,setInterval
的执行机制造成的。
通俗来说:每个 setTimeout
产生的任务会直接 push
到任务队列中;而 setInterval
在每次把任务 push
到任务队列前,都要进行一下判断(看上次的任务是否仍在队列中,如果有则不添加,没有则添加)。
setInterval
的缺点,也就显而易见了:
- 使用
setInterval
时,某些间隔会被跳过(如果上一次执行代码没有执行,那么这次的执行代码将不会被放入队列,会被跳过) - 可能多个定时器会连续执行(上一次代码在队列中等待还没有开始执行,然后定时器又添加第二次代码,第一次代码等待时间和执行时间刚好等于第二次代码执行)
二、setTimeout实现SetInterval
const myInterval = (fn, time) => {
// 定义一个递归函数持续调用定时器
let executor = (fn, time) => {
timer[key] = setTimeout(() => {
fn();
executor(fn, time);
}, time)
}
executor(fn, time);
}
测试:myInterval(()=>{console.log(111)},3000);
测试结果没有问题,那我们如何实现类似于clearSetInterval
方法,清除计时器呢?
先来研究一下setTimeOut
setTimeout
返回的竟然是一串整数,并且这些整数都不重复,还是连续的,没错,这个就是clearTimeout
工作的时候,会找到对应的id
的定时器,然后清除掉,好了,知道了这个,那么我们能不能把当前定义的mySetInterval
的id
存储下来呢?
var timeWorker = {
}
var mySetInterval= function(fn, time) {
// 定义一个key,来标识此定时器
var key = Symbol();
// 定义一个递归函数,持续调用定时器
var execute = function(fn, time) {
timeWorker[key] = setTimeout(function(){
fn();
execute(fn, time);
}, time)
}
execute(fn, time);
// 返回key
return key;
}
var myClearInterval = function(key) {
if (key in timeWorker) {
clearTimeout(timeWorker[key]);
delete timeWorker[key];
}
}
测试一下:
let time1 = mySetInterval(() => {
console.log(111)}, 3000);
let time2 = mySetInterval(() => {
console.log(222)}, 3000);
setTimeout(() => {
myClearInterval(time2);
}, 4000)
实现完成✌️!