daily-plan
daily-plan copied to clipboard
从 HTML 官方规范解读 event loop
https://html.spec.whatwg.org/multipage/webappapis.html#task-queue
事件循环
-
从任务队列中取出一个任务并执行。
-
检查微任务队列,清空微任务,如果在微任务的执行中又加入了新的微任务,也会在这一步一起执行。
-
进入更新渲染阶段,判断是否需要渲染,这里有一个
rendering opportunity
的概念,也就是说不一定每一轮 event loop 都会对应一次浏览器渲染,要根据屏幕刷新率、页面性能、页面是否在后台运行来共同决定,通常来说这个渲染间隔是固定的。(所以多个 task 很可能在一次渲染之间执行)- 浏览器会尽可能的保持帧率稳定,例如页面性能无法维持60fps(每16.66ms渲染一次)的话,那么浏览器就会选择30fps的更新速率,而不是偶尔丢帧。
- 如果浏览器上下文不可见,那么页面会降低到4fps左右甚至更低。
- 如果满足以下条件,也会跳过渲染:
- 浏览器判断更新渲染不会带来视觉上的改变。
-
map of animation frame callbacks
为空,也就是帧动画回调为空,可以通过requestAnimationFrame
来请求。
-
对于需要渲染的文档,如果窗口的大小发生了变化,执行监听的
resize
方法。 -
对于需要渲染的文档,如果页面发生了滚动,执行
scroll
方法。 -
对于需要渲染的文档,执行帧动画回调,也就是
requestAnimationFrame
的回调。 -
对于需要渲染的文档, 执行IntersectionObserver的回调,也许你在图片懒加载的逻辑里用过这个api。 待续 https://w3c.github.io/requestidlecallback/#start-an-idle-period-algorithm。
requestIdleCallback
以下内容中
requestAnimationFrame
简称为rAF
,requestIdleCallback
简称rIC
。
MDN文档中的幕后任务协作调度 API 介绍的比较清楚,来根据里面的概念做个小实验:
屏幕中间有个红色的方块,把MDN文档中requestAnimationFrame的范例部分的动画代码直接复制过来。
这个动画的例子很简单,就是利用rAF
在每帧渲染前的回调中把方块的位置向右移动10px。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
#SomeElementYouWantToAnimate {
height: 200px;
width: 200px;
background: red;
}
</style>
</head>
<body>
<div id="SomeElementYouWantToAnimate"></div>
<script>
var start = null
var element = document.getElementById("SomeElementYouWantToAnimate")
element.style.position = "absolute"
function step(timestamp) {
if (!start) start = timestamp
var progress = timestamp - start
element.style.left = Math.min(progress / 10, 200) + "px"
if (progress < 2000) {
window.requestAnimationFrame(step)
}
}
// 动画
window.requestAnimationFrame(step)
// 空闲调度
window.requestIdleCallback(() => {
alert('rIC')
})
// TODO +timeout
</script>
</body>
</html>
注意在最后我加了一个 requestIdleCallback
的函数,回调里会 alert('rIC')
,来看一下演示效果:
alert
在最开始的时候就执行了,为什么会这样呢一下,想一下「空闲」的概念,我们每一帧仅仅是把 left
的值移动了一下,做了这一个简单的渲染,没有占满空闲时间,所以可能在最开始的时候,浏览器就找到机会去调用 rIC
的回调函数了。
我们简单的修改一下 step
函数,在里面加一个很重的任务,1000次循环打印。
function step(timestamp) {
if (!start) start = timestamp
var progress = timestamp - start
element.style.left = Math.min(progress / 10, 200) + "px"
let i = 1000
while (i > 0) {
console.log("i", i)
i--
}
if (progress < 2000) {
window.requestAnimationFrame(step)
}
}
再来看一下它的表现:
其实和我们预期的一样,由于浏览器的每一帧都"太忙了",导致它真的就无视我们的 rIC
函数了。
如果给 rIC
函数加一个 timeout
呢:
// 空闲调度
window.requestIdleCallback(
() => {
alert("rID")
},
{ timeout: 500 },
)
浏览器会在大概 500ms
的时候,不管有多忙,都去强制执行 rIC
函数,这个机制可以防止我们的空闲任务被“饿死”。