requestAnimationFrame和requestIdleCallback是宏任务还是微任务

2,685 阅读4分钟

一、思考

大家都知道异步任务分为宏任务和微任务,不知道的可以看我的另一篇文章(深入理解Event Loop的运行机制), 要搞清楚requestAnimationFrame和requestIdleCallback是宏任务还是微任务就必须要搞清楚下面几个问题:

  1. 浏览器在每一轮Event Loop事件循环中都会去渲染屏幕吗?
  2. requestAnimationFrame在哪个阶段执行,是在渲染前还是渲染后?是在微任务执行前还是执行后?
  3. requestIdleCallback在哪个阶段执行,是在渲染前还是渲染后? 是在微任务执行前还是执行后?

二、任务的执行时机

在浏览器的Event Loop中是有多个任务队列的,每个任务队列的执行时机是不一样的,下面直接上干货,说说浏览器执行任务的顺序

  1. 从task任务队列中取第一个task(比如setTimeout、setIntervel的回调,也可以将同一轮循环中的所有同步代码看作是一个宏任务),执行它。
  2. 执行微任务队列里的所有微任务。
  3. 浏览器判断是否更新渲染屏幕,如果需要重新绘制,则执行步骤4-13,如果不需要重新绘制,则流程回到步骤1,这样不断循环。
  4. 触发resize、scroll事件,建立媒体查询(执行一个任务中如果生成了微任务,则执行完任务该后就会执行所有的微任务,然后再执行下一个任务)。
  5. 建立css动画(执行一个任务中如果生成了微任务,则执行完该任务后就会执行所有的微任务,然后再执行下一个任务)。
  6. 执行requestAnimationFrame回调(执行一个任务中如果生成了微任务,则执行完该任务后就会执行所有的微任务,然后再执行下一个任务)。
  7. 执行 IntersectionObserver 回调(执行一个任务中如果生成了微任务,则执行完该任务后就会执行所有的微任务,然后再执行下一个任务)。
  8. 更新渲染屏幕。
  9. 浏览器判断当前帧是否还有空闲时间,如果有空闲时间,则执行步骤10-12。
  10. 从 requestIdleCallback回调函数队列中取第一个,执行它。
  11. 执行微任务队列里的所有微任务。
  12. 流程回到步骤9,直到requestIdleCallback回调函数队列清空或当前帧没有空闲时间。
  13. 流程回到步骤1,这样不断循环。

三、代码验证

我们可以写一些代码到Chrome浏览器中验证一下,看看requestAnimationFrame和requestIdleCallback的执行顺序是怎样的。

运行以下这段代码

requestAnimationFrame(()=>{
    console.log(111);
    setTimeout(() => {
        console.log(222);
    });
    Promise.resolve().then(() => {
        console.log(333);
    });
})

requestAnimationFrame(() => {
    console.log(444);
    Promise.resolve().then(() => {
        console.log(555);
    });
})

输出结果

运行以下这段代码

requestIdleCallback(() => {
    console.log(111);
    setTimeout(() => {
        console.log(222);
    })
    Promise.resolve().then(() => {
        console.log(333);
    })
})

requestIdleCallback(() => {
    console.log(444);
    Promise.resolve().then(() => {
        console.log(555);
    })
})

输出结果

运行以下这段代码

Promise.resolve().then(() => {
    console.log(111);
    setTimeout(() => {
        console.log(222);
    })
    Promise.resolve().then(() => {
        console.log(333);
    })
})

Promise.resolve().then(() => {
    console.log(444);
    Promise.resolve().then(() => {
        console.log(555);
    })
})

输出结果

03.png

四、总结

  1. requestAnimationFrame和requestIdleCallback是和宏任务性质一样的任务,只是他们的执行时机不同而已。也有人说它们既不是宏任务也不是微任务,其实我们不必纠结这个,我们所要做的就是知道他们的执行时机就好。
  2. 浏览器在每一轮Event Loop事件循环中不一定会去重新渲染屏幕,会根据浏览器刷新率以及页面性能或是否后台运行等因素判断的,浏览器的每一帧是比较固定的,会尽量保持60Hz的刷新率运行,每一帧中间可能会进行多轮事件循环。
  3. requestAnimationFrame回调的执行与task和microtask无关,而是与浏览器是否渲染相关联的。它是在浏览器渲染前,在微任务执行后执行。
  4. requestIdleCallback是在浏览器渲染后有空闲时间时执行,如果requestIdleCallback设置了第二个参数timeout,则会在超时后的下一帧强制执行。

更多个人文章

  1. 两个跨域页面进行跳转传参的终极方案
  2. 彻底搞懂盒子模型
  3. hashHistory和browserHistory的区别
  4. 面试秘籍之手写系列
  5. 十分钟带你入门Chrome插件开发