瀏覽器的進(jìn)程模型
何為進(jìn)程?

何為線程?
一個(gè)進(jìn)程至少有一個(gè)線程,進(jìn)程開(kāi)啟后會(huì)自動(dòng)創(chuàng)建一個(gè)線程來(lái)運(yùn)行代碼,這個(gè)線程叫主線程,主線程結(jié)束,則進(jìn)程結(jié)束。
如果程序想同時(shí)執(zhí)行多塊代碼,主線程會(huì)啟動(dòng)更多的線程來(lái)執(zhí)行代碼,所以一個(gè)進(jìn)程可以有多個(gè)線程。

瀏覽器有哪些進(jìn)程和線程?
瀏覽器是一個(gè)多進(jìn)程多線程的應(yīng)用程序
瀏覽器右上角三個(gè)點(diǎn)那里的更多工具
打開(kāi)任務(wù)管理器:

每打開(kāi)一個(gè)標(biāo)簽頁(yè)就會(huì)開(kāi)啟一個(gè)渲染線程,嘗試多打開(kāi)一個(gè)標(biāo)簽頁(yè),任務(wù)管理器會(huì)多一個(gè)線程。
主要有下面三個(gè):
瀏覽器進(jìn)程
:主要負(fù)責(zé)界面顯示(是指瀏覽器窗體那些界面的交互,而非標(biāo)簽頁(yè)面內(nèi)容),用戶(hù)交互、子進(jìn)程管理等,瀏覽器進(jìn)程內(nèi)部會(huì)啟動(dòng)多個(gè)線程處理不同的任務(wù)。 
網(wǎng)絡(luò)進(jìn)程
負(fù)責(zé)加載網(wǎng)絡(luò)資源,網(wǎng)絡(luò)進(jìn)程內(nèi)部會(huì)啟動(dòng)多個(gè)線程處理不同的任務(wù)。
渲染進(jìn)程
渲染進(jìn)程啟動(dòng)后,會(huì)開(kāi)啟一個(gè)渲染主線程,主線程負(fù)責(zé)執(zhí)行 HTML、CSS、JS 代碼
默認(rèn)情況下,瀏覽器會(huì)為每個(gè)標(biāo)簽頁(yè)開(kāi)啟一個(gè)新的渲染進(jìn)程,以保證不同的標(biāo)簽頁(yè)之前不互相影響(以后瀏覽器可能會(huì)改變這種方式,為了減少進(jìn)程的數(shù)量,在尋求的方案比如一個(gè)站點(diǎn)開(kāi)辟一個(gè)進(jìn)程)

渲染主線程是如何工作的?
渲染主線程是瀏覽器中最繁忙的的線程,需要它處理的任務(wù)包括但不限于:
為何只能有一個(gè)渲染主線程?比如正在執(zhí)行一個(gè)js函數(shù)去修改頁(yè)面,定時(shí)器的任務(wù)也到達(dá)了時(shí)間,那么應(yīng)該執(zhí)行哪個(gè)?同時(shí)執(zhí)行的話(huà),修改了同一個(gè)地方如何選擇。
一個(gè)渲染進(jìn)程只能有一個(gè)渲染主進(jìn)程,使用消息隊(duì)列來(lái)調(diào)度任務(wù)

在最開(kāi)始的時(shí)候、渲染主線程會(huì)進(jìn)入一個(gè)無(wú)限循環(huán)
每一次循環(huán)會(huì)檢查消息隊(duì)列中是否有任務(wù)存在,如果有,就取出第一個(gè)任務(wù)執(zhí)行,執(zhí)行完一個(gè)后進(jìn)入下一次循環(huán),如果沒(méi)有,則進(jìn)入休眠狀態(tài)
其它所有線程(包括其它進(jìn)程的線程,比如點(diǎn)擊事件,計(jì)時(shí)器的回調(diào)函數(shù)等)可以隨時(shí)向消息隊(duì)列添加任務(wù)。新任務(wù)會(huì)加到消息隊(duì)列的末尾。在添加新任務(wù)時(shí),如果主線程是休眠狀態(tài),則會(huì)將其喚醒以繼續(xù)拿取任務(wù)執(zhí)行。
若干解釋
何為異步?
代碼執(zhí)行過(guò)程中,會(huì)遇到一些無(wú)法立即執(zhí)行的任務(wù),比如:
計(jì)時(shí)完成后需要執(zhí)行的任務(wù) -- setTimeout、setInterval
網(wǎng)絡(luò)通訊完成后需要執(zhí)行的任務(wù) -- XHR、Fetch
用戶(hù)操作后需要執(zhí)行的任務(wù) -- addEventListener

面試題:如何理解 JS 的異步?
Js 是一門(mén)單線程的語(yǔ)言,這是因?yàn)樗\(yùn)行在瀏覽器的渲染主線程中,而渲染主線程只有一個(gè)。
渲染主線程承擔(dān)著諸多任務(wù),比如渲染頁(yè)面、執(zhí)行 JS 、解析 CSS
如果使用同步的方式,就極有可能會(huì)導(dǎo)致主線程的阻塞,從而導(dǎo)致消息隊(duì)列的很多其它任務(wù)無(wú)法執(zhí)行,頁(yè)面無(wú)法及時(shí)更新,給用戶(hù)造成卡死現(xiàn)象。
所以瀏覽器使用異步的方式避免,具體做法是當(dāng)某些任務(wù)發(fā)生時(shí),比如計(jì)時(shí)器、網(wǎng)絡(luò)、事件監(jiān)聽(tīng),主線程將任務(wù)交給其它線程處理,自身立即結(jié)束此任務(wù)的執(zhí)行,轉(zhuǎn)而執(zhí)行后續(xù)代碼。當(dāng)其它線程完成此任務(wù)時(shí),將事先傳遞的回調(diào)函數(shù)包裝成任務(wù),加入到消息隊(duì)列的末尾排隊(duì),等待主線程的調(diào)度執(zhí)行。
在這種異步模式下,瀏覽器永不阻塞,從而最大限度的保證了單線程的流暢執(zhí)行。
js 為何會(huì)阻礙渲染?
渲染主線程只有一個(gè),js 的執(zhí)行與渲染都在主線程中,兩者無(wú)法同時(shí)執(zhí)行。react 的fiber 就是為了解決此問(wèn)題

任務(wù)有優(yōu)先級(jí)嗎?
任務(wù)沒(méi)有優(yōu)先級(jí),消息隊(duì)列有優(yōu)先級(jí),消息隊(duì)列會(huì)有多種,隨著瀏覽器的復(fù)雜度急劇提升,W3C 將不再使用宏隊(duì)列的說(shuō)法
根據(jù) W3C 的最新解釋?zhuān)?/p>
在目前 chrome 的實(shí)現(xiàn)中,至少包含了下面隊(duì)列, 微隊(duì)列 > 交互隊(duì)列 > 延時(shí)隊(duì)列
微隊(duì)列:用戶(hù)存放需要最快執(zhí)行的任務(wù),優(yōu)先級(jí)最高 (Promise、MutationObserver)
交互隊(duì)列:用戶(hù)存放用戶(hù)操作后產(chǎn)生的事件處理任務(wù),瀏覽器認(rèn)為用戶(hù)的操作必須及時(shí)響應(yīng),優(yōu)先級(jí)高 (addEventListener)
延時(shí)隊(duì)列:用戶(hù)存放計(jì)時(shí)器到達(dá)后的回調(diào)任務(wù),優(yōu)先級(jí)中 (setTimeout、setInterval)
幾道題加深理解
setTimeout(()=>{
console.log(1) // 交給計(jì)時(shí)線程,等待添加到延時(shí)隊(duì)列
},0)
console.log(2) // 執(zhí)行完后執(zhí)行延時(shí)隊(duì)列中的任務(wù)
結(jié)果:輸入 2,1

function delay(duration){
var start = Date.now();
while (Date.now() - start < duration){}
}
setTimeout(()=>{
console.log(1) // 交給計(jì)時(shí)線程,等待添加到延時(shí)隊(duì)列
},0)
delay(1000) // 阻塞主線程一秒
console.log(2) // 輸出 2,由于阻塞一秒后,計(jì)時(shí)線程已經(jīng)把任務(wù)計(jì)時(shí)完成加到延時(shí)隊(duì)列了,所以輸出 2 后立即輸出 1
結(jié)果:等待一秒后,同時(shí)輸出 2,1

setTimeout(()=>{
console.log(1) // 交給計(jì)時(shí)線程,等待添加到延時(shí)隊(duì)列
},0)
Promise.resolve().then(()=>{
console.log(2) // 立即添加到微隊(duì)列,微隊(duì)列優(yōu)先級(jí)高于延時(shí)隊(duì)列
})
console.log(3) // 全局任務(wù)
結(jié)果:輸出 3,2,1

// 只是定義,還未執(zhí)行哈
function a(){
console.log(1)
Promise.resolve().then(()=>{
console.log(2)
})
}
setTimeout(()=>{
console.log(3)
Promise.resolve().then(a)
},0)
Promise.resolve().then(()=>{
console.log(4) // 立即添加到微隊(duì)列,微隊(duì)列優(yōu)先級(jí)高于延時(shí)隊(duì)列
})
console.log(5) // 全局
結(jié)果:5,4,3,1,2
總結(jié)
什么是事件循環(huán)?
事件循環(huán)又叫消息循環(huán),是瀏覽器渲染主線程的工作方式
在 Chrome 的源碼中,它會(huì)開(kāi)啟一個(gè)不會(huì)結(jié)束的 for 循環(huán),每次循環(huán)從消息隊(duì)列中取出第一個(gè)任務(wù)執(zhí)行,其它線程只需要在合適的時(shí)候?qū)⑷蝿?wù)加到隊(duì)列末尾即可
過(guò)去把消息隊(duì)列簡(jiǎn)單分為宏隊(duì)列和微隊(duì)列,這種說(shuō)法目前已無(wú)法滿(mǎn)足復(fù)雜的瀏覽器環(huán)境,取而代之的是一種更加靈活多變的方式:
根據(jù) W3C 官方的解釋?zhuān)總€(gè)任務(wù)有不同的類(lèi)型,同類(lèi)型的任務(wù)必須在同一個(gè)隊(duì)列,不同任務(wù)可以屬于不同隊(duì)列。
不同任務(wù)有不同的優(yōu)先級(jí),在一次事件循環(huán)中,由瀏覽器自行決定取哪個(gè)隊(duì)列的任務(wù)。
但瀏覽器必須有一個(gè)微隊(duì)列,微隊(duì)列的任務(wù)一定具有最高的優(yōu)先級(jí),必須優(yōu)先調(diào)度執(zhí)行。
JS 中的計(jì)時(shí)器能做到精確計(jì)時(shí)嗎?
不行,因?yàn)?/p>
計(jì)算機(jī)硬件沒(méi)有原子鐘,無(wú)法做到精確計(jì)時(shí)
操作系統(tǒng)的計(jì)時(shí)函數(shù)本身就有少量偏差,由于 JS 的計(jì)時(shí)器最終調(diào)用的是操作系統(tǒng)的函數(shù),也就攜帶了這些偏差
按照 W3C 的標(biāo)準(zhǔn),瀏覽器實(shí)現(xiàn)計(jì)時(shí)器時(shí),如果嵌套層級(jí)超過(guò) 5 層,則會(huì)帶有 4 毫秒的最少時(shí)間,這樣在計(jì)時(shí)時(shí)間少于 4 毫秒又帶來(lái)了偏差


結(jié)語(yǔ):單線程是異步產(chǎn)生的原因,事件循環(huán)是異步的實(shí)現(xiàn)方式
查看原文
該文章在 2023/8/28 9:28:08 編輯過(guò)