一、思考
大家都知道异步任务分为宏任务和微任务,不知道的可以看我的另一篇文章(深入理解Event Loop的运行机制), 要搞清楚requestAnimationFrame和requestIdleCallback是宏任务还是微任务就必须要搞清楚下面几个问题:
- 浏览器在每一轮Event Loop事件循环中都会去渲染屏幕吗?
- requestAnimationFrame在哪个阶段执行,是在渲染前还是渲染后?是在微任务执行前还是执行后?
- requestIdleCallback在哪个阶段执行,是在渲染前还是渲染后? 是在微任务执行前还是执行后?
二、任务的执行时机
在浏览器的Event Loop中是有多个任务队列的,每个任务队列的执行时机是不一样的,下面直接上干货,说说浏览器执行任务的顺序
- 从task任务队列中取第一个task(比如setTimeout、setIntervel的回调,也可以将同一轮循环中的所有同步代码看作是一个宏任务),执行它。
- 执行微任务队列里的所有微任务。
- 浏览器判断是否更新渲染屏幕,如果需要重新绘制,则执行步骤4-13,如果不需要重新绘制,则流程回到步骤1,这样不断循环。
- 触发resize、scroll事件,建立媒体查询(执行一个任务中如果生成了微任务,则执行完任务该后就会执行所有的微任务,然后再执行下一个任务)。
- 建立css动画(执行一个任务中如果生成了微任务,则执行完该任务后就会执行所有的微任务,然后再执行下一个任务)。
- 执行requestAnimationFrame回调(执行一个任务中如果生成了微任务,则执行完该任务后就会执行所有的微任务,然后再执行下一个任务)。
- 执行 IntersectionObserver 回调(执行一个任务中如果生成了微任务,则执行完该任务后就会执行所有的微任务,然后再执行下一个任务)。
- 更新渲染屏幕。
- 浏览器判断当前帧是否还有空闲时间,如果有空闲时间,则执行步骤10-12。
- 从 requestIdleCallback回调函数队列中取第一个,执行它。
- 执行微任务队列里的所有微任务。
- 流程回到步骤9,直到requestIdleCallback回调函数队列清空或当前帧没有空闲时间。
- 流程回到步骤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);
})
})
输出结果
四、总结
- requestAnimationFrame和requestIdleCallback是和宏任务性质一样的任务,只是他们的执行时机不同而已。也有人说它们既不是宏任务也不是微任务,其实我们不必纠结这个,我们所要做的就是知道他们的执行时机就好。
- 浏览器在每一轮Event Loop事件循环中不一定会去重新渲染屏幕,会根据浏览器刷新率以及页面性能或是否后台运行等因素判断的,浏览器的每一帧是比较固定的,会尽量保持60Hz的刷新率运行,每一帧中间可能会进行多轮事件循环。
- requestAnimationFrame回调的执行与task和microtask无关,而是与浏览器是否渲染相关联的。它是在浏览器渲染前,在微任务执行后执行。
- requestIdleCallback是在浏览器渲染后有空闲时间时执行,如果requestIdleCallback设置了第二个参数timeout,则会在超时后的下一帧强制执行。
更多个人文章