瀏覽器內(nèi)核(渲染進(jìn)程)
瀏覽器的渲染進(jìn)程是多線程的!包含了哪些線程(列舉一些主要常駐線程):
GUI渲染線程
- 負(fù)責(zé)渲染瀏覽器界面,解析html,css,構(gòu)建DOM樹(shù)和RenderObject樹(shù),布局和繪制等。
- 當(dāng)界面需要重繪(Repaint)或由于某種操作引發(fā)回流(reflow)時(shí),該線程就會(huì)執(zhí)行
- 注意,GUI渲染線程與JS引擎線程是互斥的,當(dāng)JS引擎執(zhí)行時(shí)GUI線程會(huì)被掛起(相當(dāng)于被凍結(jié)了),GUI更新會(huì)被保存在一個(gè)隊(duì)列中等到JS引擎空閑時(shí)立即被執(zhí)行。
JS引擎線程
- 也稱為JS內(nèi)核,負(fù)責(zé)處理JAVAscript腳本程序。(例如V8引擎)
- JS引擎線程負(fù)責(zé)解析JavaScript腳本,運(yùn)行代碼。
- JS引擎一直等待著任務(wù)隊(duì)列中任務(wù)的到來(lái),然后加以處理,一個(gè)Tab頁(yè)(renderer進(jìn)程)中無(wú)論什么時(shí)候都只有一個(gè)JS線程在運(yùn)行JS程序
- 同樣注意,GUI渲染線程與JS引擎線程是互斥的,所以如果JS執(zhí)行的時(shí)間過(guò)長(zhǎng),這樣就會(huì)造成頁(yè)面的渲染不連貫,導(dǎo)致頁(yè)面渲染加載阻塞。
事件觸發(fā)線程
- 歸屬于瀏覽器而不是JS引擎,用來(lái)控制事件循環(huán)(可以理解,JS引擎自己都忙不過(guò)來(lái),需要瀏覽器另開(kāi)線程協(xié)助)
- 當(dāng)JS引擎執(zhí)行代碼塊如setTimeOut時(shí)(也可來(lái)自瀏覽器內(nèi)核的其他線程,如鼠標(biāo)點(diǎn)擊、AJAX異步請(qǐng)求等),會(huì)將對(duì)應(yīng)任務(wù)添加到事件線程中
- 當(dāng)對(duì)應(yīng)的事件符合觸發(fā)條件被觸發(fā)時(shí),該線程會(huì)把事件添加到待處理隊(duì)列的隊(duì)尾,等待JS引擎的處理
- 注意,由于JS的單線程關(guān)系,所以這些待處理隊(duì)列中的事件都得排隊(duì)等待JS引擎處理(當(dāng)JS引擎空閑時(shí)才會(huì)去執(zhí)行)
定時(shí)觸發(fā)器線程
- 傳說(shuō)中的setInterval與setTimeout所在線程
- 瀏覽器定時(shí)計(jì)數(shù)器并不是由JavaScript引擎計(jì)數(shù)的,(因?yàn)镴avaScript引擎是單線程的, 如果處于阻塞線程狀態(tài)就會(huì)影響記計(jì)時(shí)的準(zhǔn)確)
- 因此通過(guò)單獨(dú)線程來(lái)計(jì)時(shí)并觸發(fā)定時(shí)(計(jì)時(shí)完畢后,添加到事件隊(duì)列中,等待JS引擎空閑后執(zhí)行)
- 注意,W3C在HTML標(biāo)準(zhǔn)中規(guī)定,規(guī)定要求setTimeout中低于4ms的時(shí)間間隔算為4ms。
異步http請(qǐng)求線程
- 在XMLHttpRequest在連接后是通過(guò)瀏覽器新開(kāi)一個(gè)線程請(qǐng)求。
- 將檢測(cè)到狀態(tài)變更時(shí),如果設(shè)置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件,將這個(gè)回調(diào)再放入事件隊(duì)列中。再由JavaScript引擎執(zhí)行。
梳理瀏覽器內(nèi)核中線程之間的關(guān)系
GUI渲染線程與JS引擎線程互斥:
由于JavaScript是可操縱DOM的,如果在修改這些元素屬性同時(shí)渲染界面(即JS線程和UI線程同時(shí)運(yùn)行),那么渲染線程前后獲得的元素?cái)?shù)據(jù)就可能不一致了。
WebWorker,JS的多線程:
- 創(chuàng)建Worker時(shí),JS引擎向?yàn)g覽器申請(qǐng)開(kāi)一個(gè)子線程(子線程是瀏覽器開(kāi)的,完全受主線程控制,而且不能操作DOM)。
- JS引擎線程與worker線程間通過(guò)特定的方式通信(postMessage API,需要通過(guò)序列化對(duì)象來(lái)與線程交互特定的數(shù)據(jù))。
從Event Loop談JS的運(yùn)行機(jī)制
- JS分為同步任務(wù)和異步任務(wù)
- 同步任務(wù)都在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧
- 主線程之外,事件觸發(fā)線程管理著一個(gè)任務(wù)隊(duì)列,只要異步任務(wù)有了運(yùn)行結(jié)果,就在任務(wù)隊(duì)列之中放置一個(gè)事件。
- 一旦執(zhí)行棧中的所有同步任務(wù)執(zhí)行完畢(此時(shí)JS引擎空閑),系統(tǒng)就會(huì)讀取任務(wù)隊(duì)列,將可運(yùn)行的異步任務(wù)添加到可執(zhí)行棧中,開(kāi)始執(zhí)行。
看到這里,應(yīng)該就可以理解了:為什么有時(shí)候setTimeout推入的事件不能準(zhǔn)時(shí)執(zhí)行?因?yàn)榭赡茉谒迫氲绞录斜頃r(shí),主線程還不空閑,正在執(zhí)行其它代碼, 所以自然有誤差。
事件循環(huán)機(jī)制進(jìn)一步補(bǔ)充
- 主線程運(yùn)行時(shí)會(huì)產(chǎn)生執(zhí)行棧, 棧中的代碼調(diào)用某些api時(shí),它們會(huì)在事件隊(duì)列中添加各種事件(當(dāng)滿足觸發(fā)條件后,如ajax請(qǐng)求完畢)
- 而棧中的代碼執(zhí)行完畢,就會(huì)讀取事件隊(duì)列中的事件,去執(zhí)行那些回調(diào)
- 如此循環(huán)
- 注意,總是要等待棧中的代碼執(zhí)行完畢后才會(huì)去讀取事件隊(duì)列中的事件
事件循環(huán)進(jìn)階:macrotask與microtask
英文參考看這理
console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0); Promise.resolve().then(function() { console.log('promise1'); }).then(function() { console.log('promise2'); }); console.log('script end'); 輸出效果是: 1 2 3 4
5script start
script end
promise1
promise2
setTimeout
為什么呢?因?yàn)镻romise里有了一個(gè)一個(gè)新的概念:microtask
或者,進(jìn)一步,JS中分為兩種任務(wù)類(lèi)型:macrotask和microtask,在ECMAScript中,microtask稱為jobs,macrotask可稱為task
它們的定義?區(qū)別?簡(jiǎn)單點(diǎn)可以按如下理解:
macrotask(又稱之為宏任務(wù)),可以理解是每次執(zhí)行棧執(zhí)行的代碼就是一個(gè)宏任務(wù)(包括每次從事件隊(duì)列中獲取一個(gè)事件回調(diào)并放到執(zhí)行棧中執(zhí)行)
- 每一個(gè)task會(huì)從頭到尾將這個(gè)任務(wù)執(zhí)行完畢,不會(huì)執(zhí)行其它
- 瀏覽器為了能夠使得JS內(nèi)部task與DOM任務(wù)能夠有序的執(zhí)行,會(huì)在一個(gè)task執(zhí)行結(jié)束后,在下一個(gè) task 執(zhí)行開(kāi)始前,對(duì)頁(yè)面進(jìn)行重新渲染 (task->渲染->task->…)
microtask(又稱為微任務(wù)),可以理解是在當(dāng)前 task 執(zhí)行結(jié)束后立即執(zhí)行的任務(wù)
- 也就是說(shuō),在當(dāng)前task任務(wù)后,下一個(gè)task之前,在渲染之前
- 所以它的響應(yīng)速度相比setTimeout(setTimeout是task)會(huì)更快,因?yàn)闊o(wú)需等渲染
- 也就是說(shuō),在某一個(gè)macrotask執(zhí)行完后,就會(huì)將在它執(zhí)行期間產(chǎn)生的所有microtask都執(zhí)行完畢(在渲染前)
分別很么樣的場(chǎng)景會(huì)形成macrotask和microtask呢?
- macrotask:主代碼塊,setTimeout,setInterval等(可以看到,事件隊(duì)列中的每一個(gè)事件都是一個(gè)macrotask)
- microtask:Promise,process.nextTick等
補(bǔ)充:在node環(huán)境下,process.nextTick的優(yōu)先級(jí)高于Promise,也就是可以簡(jiǎn)單理解為:在宏任務(wù)結(jié)束后會(huì)先執(zhí)行微任務(wù)隊(duì)列中的nextTickQueue部分,然后才會(huì)執(zhí)行微任務(wù)中的Promise部分。
process.nextTick(function(){ console.log(7); }); new Promise(function(resolve){ console.log(3); resolve(); console.log(4); }).then(function(){ console.log(5); }); process.nextTick(function(){ console.log(8); });
這段代碼運(yùn)行結(jié)果是3,4,7,8,5
所以,總結(jié)下運(yùn)行機(jī)制:
- 執(zhí)行一個(gè)宏任務(wù)(棧中沒(méi)有就從事件隊(duì)列中獲取)
- 執(zhí)行過(guò)程中如果遇到微任務(wù),就將它添加到微任務(wù)的任務(wù)隊(duì)列中
- 宏任務(wù)執(zhí)行完畢后,立即執(zhí)行當(dāng)前微任務(wù)隊(duì)列中的所有微任務(wù)(依次執(zhí)行)
- 當(dāng)前宏任務(wù)執(zhí)行完畢,開(kāi)始檢查渲染,然后GUI線程接管渲染
- 渲染完畢后,JS線程繼續(xù)接管,開(kāi)始下一個(gè)宏任務(wù)(從事件隊(duì)列中獲取)