跳到主要内容

认识

2023年03月19日
柏拉文
越努力,越幸运

一、认识


setInterval() 方法重复调用一个函数或执行一个代码片段,在每次调用之间具有固定的时间间隔。

二、语法


const intervalID = setInterval(()=>{},delay,arg1,arg2,……,argn);
  • func: 要重复调用的函数,每经过指定 delay 毫秒后执行一次。第一次调用发生在 delay 毫秒之后。

  • delay: 是每次延迟的毫秒数(一秒等于 1000 毫秒),函数的每次调用会在该延迟之后发生。如果未指定,则其默认值为 0

  • arg1, ..., argN: 当定时器过期的时候,将被传递给 func 函数的附加参数。传参语法如下:

    const intervalID = setInterval(
    (...args) => {
    console.log(args);
    },
    1000,
    "哈哈",
    "嘻嘻"
    );
  • intervalID: 返回值 intervalID 是一个非零数值,用来标识通过 setInterval() 创建的定时器,这个值可以用来作为 clearInterval() 的参数来清除对应的定时器。值得注意的是,setInterval()setTimeout() 共享同一个 ID 池,并且 clearInterval()clearTimeout() 在技术上是可互换使用的。但是,我们应该匹配使用 clearInterval()clearTimeout(),以避免代码杂乱无章,并增强代码的可维护性。

三、对比


3.1 setTimeoutsetInterval 对比

  • 从功能方面:

    • setTimeout间隔一段时间之后执行一次调用

    • setInterval每间隔一段时间循环调用,直至clearInterval结束

  • 从内存方面:

    • setTimeout 间隔的时间到达后,将回调事件放入到事件触发线程的队列中,且这个操作只有一次

    • setInterval 间隔的时间到达后,也同样将回调事件放入到事件触发线程的队列中。不过下次的间隔时间到达后,回调会检测事件队列中是否已经有回调函数

      • 如果已经有回调函数: 那么该次的回调函数不会添加到事件队列中,该次的调用被废弃,这就是setInterval 调用被废弃的原因
      • 如果不存在回调函数: 回调函数再次放入事件队列,等待被执行。如果如果回调函数中处理的时长 > 间隔时长 , 那么上一个回调函数才从事件队列出去,下一个回调函数因为间隔时间到了加入事件队列。这样, 一个回调函数执行完毕,就开始执行另一个回调,造成 setInterval 没有间隔的重复执行 ---- setTimeout 模拟 setInterval 的原因: 很多情况下,我们并不能清晰的把控处理程序所消耗的时长,为了我们能按照一定的间隔周期性的触发定时器

3.2 requestAnimationFrame动画与setInterval动画、setTimeout动画对比

  • setTimeout 动画、setInterval 动画 : 会造成掉帧、卡顿、闪屏 的问题

    • 原因一: setTimeout的执行时间并不是确定的。在JS中,setTimeout任务被放进事件队列中,只有主线程执行完才会去检查事件队列中的任务是否需要执行,因此setTimeout的实际执行时间可能会比其设定的时间晚一些。

    • 原因二: 刷新频率受屏幕分辨率和屏幕尺寸的影响,因此不同设备的刷新频率可能会不同,而setTimeout只能设置一个固定时间间隔,这个时间不一定和屏幕的刷新时间相同。

    以上两种情况都会导致setTimeout的执行步调和屏幕的刷新步调不一致。在setTimeout中对dom进行操作,必须要等到屏幕下次绘制时才能更新到屏幕上,如果两者步调不一致,就可能导致中间某一帧的操作被跨越过去,而直接更新下一帧的元素,从而导致丢帧现象。

  • requestAnimationFrame动画: 与setTimeoutsetInterval相比,requestAnimationFrame最大的优势是由系统来决定回调函数的执行时机。换句话说就是,requestAnimationFrame 的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象。

四、问题


4.1 超时延迟

除了"最小延时"之外,定时器仍然有可能因为当前页面(或者操作系统/浏览器本身)被其他任务占用导致延时。 需要被强调是, 直到调用 setTimeout() 的主线程执行完其他任务之后,回调函数和代码段才能被执行。出现这个结果的原因是,尽管setTimeout0ms 的延迟来调用函数,但这个任务已经被放入了队列中并且等待下一次执行;并不是立即执行;队列中的等待函数被调用之前,当前代码必须全部运行完毕,因此这里运行结果并非预想的那样。

4.2 最小延时 >=4ms

在浏览器中,setTimeout()/setInterval() 的每调用一次定时器的最小间隔是 4ms,原因有两种:

  • 原因一、在嵌套函数调用 setTimeout() 时(嵌套级别达到特定深度时): Chrome 和 Firefox 中, 定时器的第 5 次调用被阻塞了;在 Safari 是在第 6 次;Edge 是在第 3 次

  • 原因二、已经执行的 setInterval 的回调函数阻塞

4.3 定时器为什么是不可靠的?

说明: setTimeout/setInterval无法保证准时执行回调函数,定时器的时间间隔设为0,也会有几毫秒的延迟,定时器为什么是不可靠的

  1. 超时延迟: JavaScript 中有一个主线程和一个调用栈,所有的任务都会被放到调用栈等待主线程执行。JavaScript 执行时,同步任务都在主线程上执行,前进如果产生微任务,在同步任务执行完成后清空微任务,这时候是一个完整的宏任务+微任务 完成,此后还可能会有更新渲染。之后JavaScript询问事件触发线程,是否有待执行的回调函数,如果有就会加入到执行栈中交给主线程执行。所以,如果同步任务、或者同步任务完成之后有可能触发的页面更新渲染这些耗时都有可能使得定时器回调触发延迟

  2. 最小延迟 >= 4ms: 在浏览器中,setTimeout()/setInterval() 的每调用一次定时器的最小间隔是 4ms,这通常是由于函数嵌套导致(嵌套层级达到一定深度),或者是由于已经执行的 setInterval 的回调函数阻塞导致的。

  3. 未被激活的 tabs 的定时最小延迟>=1000ms

4.4 未被激活的 tabs 的定时最小延迟 >=1000ms

为了优化后台 tab 的加载损耗(以及降低耗电量),在未被激活的 tab 中定时器的最小延时限制为 1S(1000ms)Firefoxversion 5 (see bug 633421开始采取这种机制,1000ms 的间隔值可以通过 dom.min_background_timeout_value 改变。Chromeversion 11 (crbug.com/66078) 开始采用。

4.5 谈谈你对timeoutID的认识以及取消函数的认识?

setTimeout 返回的timeoutIDsetInterval 返回的 timeoutID 共用一个编号池,技术上,clearTimeoutclearInterval 可以互换、混用。但是为了避免混淆,尽量不要混用取消定时函数。

function debounce(fn, wait) {
let timer = null;
return function (...args) {
if (timer) {
clearInterval(timer);
timer = null;
}
timer = setTimeout(() => {
fn.apply(this, args);
}, wait);
};
}