event loop拟糕,是一個比較抽象的東西屡限。主要因為兩個方面
- 文檔看上去很復(fù)雜兼耀;
- 難以眼見為實压昼。如何確認自己真的明白整個過程求冷。如何把event loop的理論聯(lián)系到實際,或者更進一步窍霞,如何用于編程和debug匠题。
作為一個習(xí)慣了所見即所得的前端,我希望能夠確認我yy出來的流程是否和瀏覽器執(zhí)行的真實情況一致但金,同時能夠真正作用于編程和debug中
console.log韭山?
最初接觸event loop(之前寫(翻譯)過的一篇博客),我曾經(jīng)嘗試過console.log
冷溃。效果其實并沒有很好钱磅,更像是,我大約知道了這個理論似枕,然后根據(jù)現(xiàn)實的情況強行解釋它盖淡。同時中間還有很大的不確定性。比如說如何確認瀏覽器是先執(zhí)行microtaks還是先執(zhí)行渲染凿歼,如何確認一個event loop已經(jīng)執(zhí)行完了褪迟。
那,還有啥子辦法答憔?
最近看了幾場jsconf eu關(guān)于chrome的performance的分享味赃,忽然靈光一現(xiàn),可以利用chrome dev tool>performance來驗證整個過程
event loop
文檔關(guān)于event loop的執(zhí)行說明如下圖:
task queue包含:
- Events
- Parsing
- Callbacks(setTimeout, etc)
- Using a resource
- Reacting to DOM manipulation(node.inserBefore, etc)
(每個task有自己的task source攀唯,瀏覽器有相當大的裁量權(quán)來決定哪個task source過來的task先執(zhí)行洁桌。)
執(zhí)行完一個taskQueue,就會執(zhí)行一次microtask侯嘀,把microtask queue全部清干凈
理論看上去是很清晰的另凌,然而,仍有地方是很容易迷惑的戒幔,比如說:
- 如何才算做一個queue吠谢?考慮以下代碼
// Promise runs in microtask
Promise.resolve().then(() => { console.log('microtask ran') })
// is it a queue?
const div = document.querySelector('div')
document.body.insertBefore(document.createElement('div'), div)
// queue ends?
console.log('will microtask run before me?')
結(jié)果似乎不是這樣:
可以看到,只有在log
結(jié)束的時候诗茎,也就是在整個執(zhí)行椆し唬空了的時候才執(zhí)行了microtask。wait敢订,這和(我理解的)文檔說的不一樣M跷邸!難道不是dom manipulation執(zhí)行完了就執(zhí)行microtask么楚午?
so, what's a queue?
根據(jù)上面昭齐,似乎是把一個執(zhí)行棧都執(zhí)行完了,就算一個queue執(zhí)行完了矾柜,就可以進行microtask阱驾。
let's have a try
function log (type) {
Promise.resolve().then(() => {
console.log(`microtask added by ${type} runs`)
})
console.log(`start log[${Date.now()}]: ${type}`)
}
setTimeout(log.bind(null, 'setTimeout'))
document.addEventListener('DOMContentLoaded', log.bind(null, 'DOMContentLoaded'))
log('global 1')
log('global 2')
確認了兩件事情:
- 當前執(zhí)行椌兔眨空時才執(zhí)行microtask
- setTimeout會在其他task queue(此處是Parsing)之后,再執(zhí)行
放大Parse HTML里覆,我們可以看到:
microtask執(zhí)行在render(recalculate style和layout)之前
microtask
文檔:
看上去還蠻清晰的
如果在一個microtask里面又指定了一個microtask呢丧荐?
考慮以下代碼:
Promise.resolve().then(log.bind(null, 'promise'))
可以看到,microtask里面又指定了一個microtask喧枷,會順序執(zhí)行虹统,而不是留到下一個microtask checkPoint執(zhí)行
render pipeline
實際上對于前面的兩個理論的驗證,單純靠console.log
隧甚,也是可以印證的窟却,但是渲染相關(guān),就很難了呻逆。
文檔
圖片來自render process model
考慮以下代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>event loop</title>
<style>
body {
height: 2000px;
background: lightgreen;
}
</style>
</head>
<body>
<div class="animate">lulala</div>
<script src="./test.js"></script>
</body>
</html>
// your test.js
document.addEventListener('DOMContentLoaded', () => {
function log (type) {
Promise.resolve().then(() => {
console.log(`microtask added by ${type} runs`)
})
console.log(`start log[${Date.now()}]: ${type}`)
}
setTimeout(() => {
window.requestAnimationFrame(log.bind(null, 'rAF'))
window.addEventListener('scroll', log.bind(null, 'scroll'))
window.scrollTo(0, 3000)
})
log('global')
})
可以看到scroll是在rAF之前被觸發(fā)的夸赫,之后便開始了update layer tree和paint
wait,竟然發(fā)現(xiàn)scroll和rAF之后咖城,都觸發(fā)了microtasks2缤取!
conclusion
哈宜雀,原本是想寫一篇深入event loop的文章切平,寫著變成了如何使用performance觀察event loop???♂
不過在整個嘗試的過程里面,還破除了一些以前模棱兩可的迷信辐董,比如說“不要老是去拿元素屬性悴品,會觸發(fā)重排或者重繪的”,其實不一定的哦简烘,設(shè)置了動畫或者對元素進行了class增刪苔严,然后取元素寬高之類的屬性,的確會導(dǎo)致重排或者重繪的計算孤澎。但是如果對元素(包括所有會影響該元素的操作)啥也沒做届氢,只是取值,并不會觸發(fā)什么事情覆旭。
??