前言
大家对前端的eventloop可能已经不是很陌生了,众所周知eventloop中存在task(macrotask)和microtask两种队列,在浏览器中,一个task执行结束的时候都会把microtask队列里面的任务全部执行完成.
但是这两种队列的任务源都有哪些,涉及到了requestAnimationFrame,点击事件等情况,页面渲染和eventloop是怎么样协作的,下面我们来探究一下.
例题
// 首先先来看一道题 渲染完成以后,点击中间的div 请问console的输出顺序
<div class="outer">
<div class="inner"></div>
</div>
let $outer = document.querySelector('.outer')
let $inner = document.querySelector('.inner')
console.log('sync coneole')
requestAnimationFrame(() => {
console.log('requestAnimationFrame render console')
})
setTimeout(() => {
console.log('macrotask console outer')
}, 0)
function onClick() {
console.log('click!')
setTimeout(() => {
console.log('setTimeout!')
}, 0)
Promise.resolve().then(() => {
console.log('Promise!')
console.log( 'sync coneole')
setTimeout(() => {
console.log( 'macrotask console')
}, 0)
Promise.resolve().then(() => {
console.log( 'microtask console')
})
console.log( 'sync console')
sync(300)
function sync(time) {
const now = Date.now()
while (Date.now() - now < time) {
}
}
})
}
$outer.addEventListener('click', onClick)
$inner.addEventListener('click', onClick)
规范
规范里面有着详细的说明 HTML5规范相关章节
下面大体说一下
1.Let oldestTask be the oldest task on one of the event loop's task queues, if any, ignoring, in the case of a window event loop, tasks whose associated Documents are not fully active. The user agent may pick any task queue. If there is no task to select, then jump to the microtasks step below.
2.Report the duration of time during which the user agent does not execute this loop by performing the following steps:
- Set event loop begin to the current high resolution time.
- If event loop end is set, then let top-level browsing contexts be the set of all top-level browsing contexts of all Document objects associated with the event loop. Report long tasks, passing in event loop end, event loop begin, and top-level browsing contexts.
3.Set the event loop's currently running task to oldestTask.
4.Run oldestTask.
5.Set the event loop's currently running task back to null.
6.Remove oldestTask from its task queue.
前几步主要讲的是从里面task里面执行一个任务
7.Microtasks: Perform a microtask checkpoint.
检查microtask,在microtask执行过程中新的microtask任务源提供的新任务也都在本次eventloop中执行
8.Let now be the current high resolution time. [HRT]
9.Report the task's duration by performing the following steps:
- Let top-level browsing contexts be an empty set.
- For each environment settings object settings of oldestTask's script evaluation environment settings object set, append setting's top-level browsing context to top-level browsing contexts.
- Report long tasks, passing in event loop begin (repurposed as meaning the beginning of the task), now (the end time of the task), top-level browsing contexts, and oldestTask.
检查渲染会不会影响帧率, eventloop并不是每一轮都会进行渲染
10.Update the rendering: if this is a window event loop, then:
UI渲染,这部分内容比较多,想详细了解的的请阅读规范 [Update the rendering] (https://html.spec.whatwg.org/multipage/webappapis.html#update-the-rendering),这里面主要讲的是渲染的准备工作:例如resize scroll 媒体查询 css动画 还有执行animation frame callbacks回调 等
之后的是渲染完成的善后步骤,和之前的HRT相关,就不详细展开了.
我们一步一步分析
-
同步输出 sync coneole
-
根据渲染节点和检查macrotask的先后顺序决定输出 requestAnimationFrame render console 还是 macrotask console outer
-
内部div同步的 click!
-
microtask中的 Promise!
-
microtask中的 两个sync coneole
-
microtask中的新加在microtask队列的 microtask console
-
外部div同步的 click!
-
microtask中的 Promise!
-
microtask中的 两个sync coneole
-
microtask中的新加在microtask队列的 microtask console
-
刚点击, 点击的maccrotask 中加在macrotask的 setTimeout!
-
点击的maccrotask 中加在macrotask的 Promise.resolve()中加在macrotask的 macrotask console
-
刚点击, 点击的maccrotask 中加在macrotask的 setTimeout!
-
点击的maccrotask 中加在macrotask的 Promise.resolve()中加在macrotask的 macrotask console
//答案是
sync coneole
(requestAnimationFrame render console)(macrotask console outer) 这两个顺序不一定
click!
Promise!
sync coneole
sync console
microtask console
click!
Promise!
sync coneole
sync console
microtask console
setTimeout!
macrotask console
setTimeout!
macrotask console
网友评论