setInterval的缺点
-
“丢帧”现象
:setInterval()
仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中,即在向队列插入代码实例之前,会先检测是否正在执行该代码实例,以及队列中是否已经存在该代码实例,只要存在其一就都不会再向队列中插入。 -
实际代码执行间隔 <= 设定的时间间隔
:比如一个1000ms的interval,在插入队列后因为前一个任务执行超时(假设超时800ms),导致该任务不能及时执行,当它执行完毕后(假设执行不耗时,即还剩下200ms),下一个代码(相隔200ms后)又立即添加到队列中,并立即执行,使得实际代码执行间隔(仅200ms)小于设定的间隔时间,极端条件下可能会出现代码实例连续执行的情况 -
执行间隔变小实验代码
// 在执行setInterval插入的第一个代码实例之前,插入一个耗时任务 setTimeout(()=>{ for (let i=0; i<1000000000; i++); }, 1000) // setInterval插入的代码,每次会打印执行的时刻 let timer = setInterval(()=>{ console.log(new Date().getTime()) }, 1000) // 为了让输出简单点,清除掉间隔定时器 setTimeout(()=>{ clearInterval(timer) }, 2000) // 1615628598117 // 1615628598315 // 可以看到,两个代码实例执行的间隔仅 约 200ms,违背了最初 1000ms 执行间隔的初衷
使用setTimeout代替
-
示例代码
let mySetInterval = function (fn, interval, ...args) { // 将需要借助的变量限制在闭包内,避免污染全局空间 let timer = {} function setInt (fn, interval) { timer.timer = setTimeout(()=>{ fn(...args) // 不断启用新的setTimeout setInt(fn, interval) }, interval) } setInt(fn, interval) // 必须返回引用,否则下一个setTimeout执行时,timer.timer将更新 return timer } // 启动间隔定时器 let timer = mySetInterval(()=>{ console.log(1) }, 1000) // 清除间隔定时器 clearTimeout(timer.timer)
-
解决的问题:
- 使用
setTimeout
不会有代码实例“丢帧”现象
- 保证了代码的执行间隔大于等于预期时间,但仍然不能保证代码可以按时执行
- 使用
测试 mySetInterval
- 实验代码
let mySetInterval = function (fn, interval, ...args) { // 将需要借助的变量限制在闭包内,避免污染全局空间 let timer = {} function setInt (fn, interval) { timer.timer = setTimeout(()=>{ fn(...args) // 不断启用新的setTimeout setInt(fn, interval) }, interval) } setInt(fn, interval) // 必须返回引用,否则下一个setTimeout执行时,timer.timer将更新 return timer } // 在执行setInterval插入的第一个代码实例之前,插入一个耗时任务 setTimeout(()=>{ for (let i=0; i<1000000000; i++); }, 1000) // setInterval插入的代码,每次会打印执行的时刻 let timer = mySetInterval (()=>{ console.log(new Date().getTime()) }, 1000) // 为了让输出简单点,清除掉间隔定时器 setTimeout(()=>{ clearTimeout(timer.timer) }, 3000) /* 注意这里跟刚刚不一样了,因为前一个耗时大约800ms, mySetInterval插入的第二个代码实例的执行时刻大约为: 1000ms(耗时操作执行的开始时间) + 800ms(耗时操作的执行时间) + 1000ms(mySetInterval插入的第二个代码实例的预定执行时间) == 2800ms, 因此此处设置的清除超时定时器的预定时间需要在2800ms以上 */ // 1615632232151 // 1615632233152