在現代 Web 開發中,了解瀏覽器如何渲染頁面以及 JavaScript 如何影響頁面加載流程可以更好地理解前端開發的核心原理。
一、瀏覽器渲染進程概述
瀏覽器的渲染進程(Render 進程)主要負責頁面的渲染、腳本執行和事件處理等工作。為了避免因單個頁面崩潰而導致整個瀏覽器崩潰,每個頁面都有獨立的渲染進程。
Render 進程是一個多線程架構,包含以下主要線程:
-
GUI 渲染線程
-
負責渲染瀏覽器界面,包括解析 HTML 和 CSS,構建 DOM 樹和 RenderObject 樹,以及執行布局和繪制等操作。
-
當界面需要重繪(Repaint)或由于操作引發回流(Reflow)時,該線程會被激活。
-
注意:GUI 渲染線程與 JavaScript 引擎線程是互斥的。當 JavaScript 引擎執行時,GUI 線程會被掛起,所有需要更新的 GUI 操作會被暫存到一個隊列中,只有當 JavaScript 引擎空閑時,這些操作才會被執行。
-
-
JS 引擎線程
-
也稱為 JavaScript 內核,如 V8 引擎,負責處理 JavaScript 腳本程序。
-
單線程特性 :JavaScript 主線程一次只能執行一個任務(同步代碼),同步任務按順序依次執行,不會阻塞其他 JavaScript 代碼的執行。
-
任務隊列(Task Queue) :異步任務的回調函數會被放置在這個隊列中。有兩種類型的任務隊列:
-
宏任務(macrotask)隊列 :例如
setTimeout、setInterval的回調,網絡請求的回調,Document Object的事件等。 -
微任務(microtask)隊列 :由
Promise、MutationObserver、process.nextTick(Node.js 環境)等生成的回調。
-
-
事件循環(Event Loop)機制
-
執行規則 :事件循環不斷檢測主線程是否空閑,如果空閑就從任務隊列中取出任務執行。
-
工作過程 :
-
瀏覽器環境或 Node.js 環境中,JavaScript 引擎(如 V8)執行同步代碼,完成主線程上的所有同步任務。
-
如果遇到異步任務(如
setTimeout或setInterval的回調),它們會被暫時擱置在相應的任務隊列中。 -
引擎會監聽宏任務和微任務隊列中的任務:
-
微任務隊列 的優先級高于 宏任務隊列。在每次執行完主線程的一個同步任務之后,會先檢查微任務隊列,按順序執行所有微任務,直到微任務隊列空。
-
然后引擎會檢查宏任務隊列,取出一個任務執行。
-
-
-
-
-
事件觸發線程
-
歸屬于瀏覽器而非 JavaScript 引擎,用于控制事件循環。
-
當執行諸如
Promise等操作時,相關任務會被添加到事件線程中。當事件觸發條件滿足時,線程會將事件添加到待處理隊列的隊尾,等待 JavaScript 引擎的處理。
-
-
定時觸發器線程
-
專門用于處理
setTimeout和setInterval等定時任務。 -
瀏覽器的定時計數器不依賴 JavaScript 引擎計數(因為 JavaScript 引擎是單線程的,若處于阻塞狀態會影響計時的準確性)。當定時觸發器線程計時完成后,會通知事件觸發線程,將定時任務的回調函數添加到事件隊列的隊尾。
-
后臺定時觸發器線程計時的準確性問題:
-
當瀏覽器處于后臺時,為了節省系統資源和電量消耗,瀏覽器可能會對定時觸發器線程的執行頻率進行優化調整。將
setTimeout和setInterval的執行間隔延長,導致原本應該按照設定時間間隔執行的任務被延遲執行,從而出現計時不準確現象,如果有需要定時觸發器線程要必須在后臺執行強需求的話可以使用。- 開啟JS多線程web weoker,倒計時寫在weborker里時,頁面的tab不會影響到倒計時的計算
let webWorkDate = 100, date = 100; // 開啟線程 const work = new Worker('worker.js'); setInterval(() => { date--; console.log('普通倒計數:', date); }, 1000); // 傳輸數據 work.postMessage({ time: webWorkDate }); console.log(work); // 監聽線程 work.onmessage = (event) => { console.log(); console.log('Worker倒計數:', event.data.num); if (event.data.num === 0) { work.terminate(); //關閉線程 } }; //worker.js self.addEventListener( 'message', function (e) { setInterval(() => { let num = e.data.time--; self.postMessage({ num }); }, 1000); }, false );
-
-
-
異步 HTTP 請求線程
-
在使用
XMLHttpRequest或fetch等進行網絡請求時,瀏覽器會開啟一個新的線程負責請求。 -
當檢測到狀態變更時,若設置有回調函數,異步線程會生成狀態變更事件,并將回調函數放入事件隊列中,等待 JavaScript 引擎執行。
-
二、頁面加載的整體執行步驟
-
加載整體 HTML 文件
- 瀏覽器首先會加載整個 HTML 文件,解析其結構。
-
解析 HTML 并建立 DOM 樹
-
瀏覽器會從上到下解析 HTML 文件,構建 DOM 樹。在解析過程中,遇到諸如
<script>、<link>等標簽時,會下載和解析相應的內容。 -
如果是
<link>標簽,瀏覽器會解析 CSS 文件并構建 CSS 對象模型(CSSOM 樹)。
-
-
結合 DOM 和 CSSOM 樹生成 Render 樹
- Render 樹是 DOM 樹和 CSSOM 樹的結合體,用于描述頁面中可見元素的布局和樣式信息。
-
布局 Render 樹(Layout/Reflow)
- 負責計算各元素的尺寸和位置等布局信息。
-
繪制 Render 樹(Paint)
- 根據 Render 樹中的信息,繪制頁面的像素內容。
-
GPU 合成
- 瀏覽器會將各層的信息發送給 GPU,GPU 會將各層合成,并顯示在屏幕上。
三、HTML、CSS 和 JavaScript 的解析與執行
-
HTML 解析
-
瀏覽器從上到下解析 HTML 文件,構建 DOM 樹。
-
遇到
<script>標簽時,若腳本是內部腳本,瀏覽器會立即解析并執行;若是外部腳本,瀏覽器會暫停解析 HTML,等待腳本下載完成后執行。
-
-
CSS 解析
- CSS 有三種聲明方式:外聯樣式表、內聯樣式表和內部樣式表。瀏覽器會根據這些樣式構建 CSSOM 樹,用于渲染頁面的樣式。
-
JavaScript 解析
- JavaScript 引擎負責解析和執行 JavaScript 腳本。執行 JavaScript 腳本時會阻塞 HTML 解析,因此建議將
<script>標簽放置在頁面底部,以減少對頁面加載的影響。
- JavaScript 引擎負責解析和執行 JavaScript 腳本。執行 JavaScript 腳本時會阻塞 HTML 解析,因此建議將
四、DOM 文檔加載步驟
-
解析 HTML 結構:瀏覽器解析 HTML 文件,構建 DOM 樹。
-
加載外部腳本和樣式文件:加載外部的 JavaScript 和 CSS 文件。
-
解析并執行腳本代碼:解析和執行 JavaScript 腳本。
-
執行事件綁定代碼:如
$(function(){})中的代碼。 -
加載二進制資源:加載圖片等二進制資源。
-
頁面加載完畢:執行
window.onload事件。
希望本文能幫助你更深入地理解瀏覽器的工作原理,關于如何優化頁面加載性能咱們下回接著再聊。
浙公網安備 33010602011771號