瀏覽器的運行機制—3.瀏覽器的渲染進程

重點來了,我們可以看到,上面提到了這么多的進程,那么,對于普通的前端操作來說,最終要的是什么呢?答案是渲染進程。因為頁面的渲染,JS的執(zhí)行,事件的觸發(fā),都在這個進程內進行的。接下來重點分析這個進程。

之前講到過,進程一般是多線程的,忘了可以再復習下前面第一節(jié),進程和線程,那么瀏覽器的渲染進程又包括哪些線程。

1.渲染進程包括哪些線程

  1. GUI渲染線程
  • 負責渲染瀏覽器界面,解析HTML,CSS,構建DOM樹和RenderObject樹,布局和繪制等。
  • 當界面需要重繪(Repaint)或由于某種操作引發(fā)回流(reflow)時,該線程就會執(zhí)行
  • 注意,GUI渲染線程與JS引擎線程是互斥的,當JS引擎執(zhí)行時GUI線程會被掛起(相當于被凍結了),GUI更新會被保存在一個隊列中等到JS引擎空閑時立即被執(zhí)行。
  1. JS引擎線程(單線程)
  • 也稱為JS內核,負責處理Javascript腳本程序。(例如常常聽到的谷歌瀏覽器的V8引擎,新版火狐的JaegerMonkey引擎等)
  • JS引擎線程負責解析Javascript腳本,運行代碼。
  • JS引擎一直等待著任務隊列中任務的到來,然后加以處理,一個Tab頁(renderer進程)中無論什么時候都只有一個JS線程在運行JS程序
  • 同樣注意,GUI渲染線程與JS引擎線程是互斥的,所以如果JS執(zhí)行的時間過長,這樣就會造成頁面的渲染不連貫,導致頁面渲染加載阻塞。
  1. 事件觸發(fā)線程
  • 歸屬于渲染進程而不是JS引擎,用來控制事件輪詢(可以理解,JS引擎自己都忙不過來,需要瀏覽器另開線程協(xié)助)
  • 當JS引擎執(zhí)行代碼塊如鼠標點擊、AJAX異步請求等,會將對應任務添加到事件觸發(fā)線程中
  • 當對應的事件符合觸發(fā)條件被觸發(fā)時,該線程會把事件添加到待處理任務隊列的隊尾,等待JS引擎的處理
  • 注意,由于JS的單線程關系,所以這些待處理隊列中的事件都得排隊等待JS引擎處理(當JS引擎空閑時才會去執(zhí)行)
  1. 定時觸發(fā)器線程

    • 定時器setInterval與setTimeout所在線程
    • 瀏覽器定時計數(shù)器并不是由JavaScript引擎計數(shù)的,(因為JavaScript引擎是單線程的, 如果任務隊列處于阻塞線程狀態(tài)就會影響記計時的準確)
    • 因此通過單獨線程來計時并觸發(fā)定時(計時完畢后,添加到事件隊列中,等待JS引擎空閑后執(zhí)行)
    • 注意,W3C在HTML標準中規(guī)定,規(guī)定要求setTimeout中低于4ms的時間間隔算為4ms。
  2. 異步http請求線程

    • 用于處理請求XMLHttpRequest,在連接后是通過瀏覽器新開一個線程請求。如ajax,是瀏覽器新開一個http線程
    • 將檢測到狀態(tài)變更(如ajax返回結果)時,如果設置有回調函數(shù),異步線程就產生狀態(tài)變更事件,將這個回調再放入js引擎線程的事件隊列中。再由JavaScript引擎執(zhí)行。


      2084336019-5a65972413011.png

知道了這幾個線程,那么通過這幾個線程,js是怎么執(zhí)行的呢?

2.渲染進程中的線程之間的關系

GUI渲染線程與JS引擎線程互斥

由于JavaScript是可操縱DOM的,如果在修改這些元素屬性同時渲染界面(即JS線程和GUI線程同時運行),那么渲染線程前后獲得的元素數(shù)據(jù)就可能不一致了。

因此為了防止渲染出現(xiàn)不可預期的結果,瀏覽器設置GUI渲染線程與JS引擎為互斥的關系,當JS引擎執(zhí)行時GUI線程會被掛起,
GUI更新則會被保存在一個隊列中等到JS引擎線程空閑時立即被執(zhí)行。

JS阻塞頁面加載

從上述的互斥關系,可以推導出,JS如果執(zhí)行時間過長就會阻塞頁面。

譬如,假設JS引擎正在進行巨量的計算,所以JS引擎很可能很久很久后才能空閑,所以導致頁面渲染加載阻塞。這就牽扯到script標簽在html中的存放位置。具體可以看我另一篇文章 為什么script標簽一般放在body下面

3.js引擎是單線程的

我們知道js是單線程的。也就是說,同一個時間只能做一件事。那么,為什么JavaScript不能有多個線程呢?這樣能提高效率啊。

參考阮一峰大神的文章js事件輪詢(Event Loop)

  • JavaScript的單線程,與它的用途有關。作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很復雜的同步問題。比如,假定JavaScript同時有兩個線程,一個線程在某個DOM節(jié)點上添加內容,另一個線程刪除了這個節(jié)點,這時瀏覽器應該以哪個線程為準?
  • 所以,為了避免復雜性,從一誕生,JavaScript就是單線程,這已經成了這門語言的核心特征,將來也不會改變。
  • 為了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript腳本創(chuàng)建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個新標準并沒有改變JavaScript單線程的本質。

4.js事件輪詢

上面我們已經知道JS引擎是單線程,任務應該是按順序執(zhí)行的,那么怎么會有同步異步之說?

  • 單線程就意味著,所有任務需要排隊,前一個任務結束,才會執(zhí)行后一個任務。如果前一個任務耗時很長,后一個任務就不得不一直等著。
  • 如果排隊是因為計算量大,CPU忙不過來,倒也算了,但是很多時候CPU是閑著的,因為IO設備(輸入輸出設備)很慢(比如Ajax操作從網(wǎng)絡讀取數(shù)據(jù)),不得不等著結果出來,再往下執(zhí)行。
  • JavaScript語言的設計者意識到,這時主線程完全可以不管IO設備,掛起處于等待中的任務,先運行排在后面的任務。等到IO設備返回了結果,再回過頭,把掛起的任務繼續(xù)執(zhí)行下去。
  • 于是,所有任務可以分成兩種,一種是同步任務(synchronous),另一種是異步任務(asynchronous)。同步任務指的是,在主線程上排隊執(zhí)行的任務,只有前一個任務執(zhí)行完畢,才能執(zhí)行后一個任務;異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務可以執(zhí)行了,該任務才會進入主線程執(zhí)行。

理解了同步異步。其實其最本質原因就是基于js的事件輪詢機制。

  1. 所有同步任務都在主線程(即js引擎線程)上執(zhí)行,形成一個執(zhí)行棧
  2. 而異步任務均由事件觸發(fā)線程控制,其有一個任務隊列。只要異步任務有了運行結果,就在"任務隊列"之中放置回調事件。異步任務必須指定回調函數(shù),當主線程開始執(zhí)行異步任務,就是執(zhí)行對應的回調函數(shù)。所以所謂"回調函數(shù)"(callback),就是那些會被主線程掛起來的代碼。
  3. 一旦"執(zhí)行棧"中的所有同步任務執(zhí)行完畢,系統(tǒng)就會讀取"任務隊列",按順序結束等待狀態(tài),進入執(zhí)行棧,開始執(zhí)行。
  4. 主線程不斷重復上面的第三步
  5. 只要主線程空了,就會去讀取"任務隊列",這個過程會不斷重復。這就是JavaScript的運行機制。又稱為Event Loop(事件循環(huán)或者輪詢)。

5.定時器觸發(fā)線程

上述事件循環(huán)機制的核心是:JS引擎線程和事件觸發(fā)線程

js來控制主線程,事件觸發(fā)來控制任務隊列就如主線程。

為什么要單獨的定時器線程?因為JavaScript引擎是單線程的, 如果處于阻塞線程狀態(tài)就會影響記計時的準確,因此很有必要單獨開一個線程用來計時。

什么時候會用到定時器線程?當使用setTimeout或setInterval時,它需要定時器線程計時,計時完成后就會將特定的事件推入事件觸發(fā)線程的任務隊列中。等待進入主線程執(zhí)行。

譬如:

setTimeout(function(){
    console.log('hello!');
}, 1000);

這段代碼的作用是當1000毫秒計時完畢后(由定時器線程計時),將回調函數(shù)推入事件隊列中,等待主線程執(zhí)行

setTimeout(function(){
    console.log('hello!');
}, 0);

console.log('begin');

//begin hello

這段代碼的效果是表示當前代碼執(zhí)行完(執(zhí)行棧清空)以后,立即執(zhí)行(0毫秒間隔)指定的回調函數(shù)。

注意:

  • 雖然代碼的本意是0毫秒后就推入事件隊列,但是html5標準中規(guī)定,規(guī)定要求setTimeout中低于4ms的時間間隔算為4ms。
  • 就算不等待4ms,就算假設0毫秒就推入事件隊列,也會先執(zhí)行begin(因為只有主線程可執(zhí)行棧內空了后才會主動讀取事件隊列)。要是當前代碼耗時很長,有可能要等很久,所以并沒有辦法保證,回調函數(shù)一定會在setTimeout()指定的時間執(zhí)行。同理setInterval則是每次都精確的隔一段時間推入一個事件(但是,事件的實際執(zhí)行時間不一定就準確,還有可能是這個事件還沒執(zhí)行完畢,下一個事件就來了)

6.總結

這里我沒有總結css渲染這塊,由于內容較多,我會另開一篇文章來講解。

寫了這么多,還是要感謝大神們的文章。

js事件輪詢(Event Loop)

從瀏覽器多進程到JS單線程,JS運行機制最全面的一次梳理

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容