前言
寫這篇文章的初衷:
記得最開始學前端知識時,是一點一點的積累,一個知識點一個知識點的攻克。 就這樣,雖然在很長一段時間內積累了不少的知識,但是,總是無法將它串聯到一起。每次梳理時都是很分散的,無法保持思路連貫性。 直到最近,在將DNS域名解析、建立TCP連線、構建HTTP請求、瀏覽器渲染過程‘’流程梳理一遍後,感覺就跟打通了任督二脈一樣,有了一個整體的架構,以前的知識點都連貫起來了,至少現在知道了它的大部分骨架。 梳理出一個知識體系,以後就算再學新的知識,也會盡量往這個體系上靠攏,環環相扣,更容易理解,也更不容易遺忘。這也是本文的目標。
以 前端領域 的知識為重點,並且本文內容超多,建議先了解主幹,然後分批次閱讀。 這篇文章真的寫了好久好久…
一、梳理主幹流程
知識體系中,最重要的是骨架,脈絡。有了骨架後,才方便填充細節。所以,先梳理下主幹流程:
- 瀏覽器接收url並開啟一個新程序(這一部分可以展開瀏覽器的程序與執行緒的關係)
- 瀏覽器解析輸入的 URL,提取出其中的協議、域名和路徑等資訊。(這部分涉及URL組成部分)
- 瀏覽器向 DNS 伺服器傳送請求,DNS伺服器透過 多層查詢 將該 域名 解析為對應的 IP地址 ,然後將請求傳送到該IP地址上,與 伺服器 建立連線和交換資料。(這部分涉及DNS查詢)
- 瀏覽器與伺服器建立 TCP 連線。(這部分涉及TCP三次握手/四次揮手/5層網路協議)
- 瀏覽器向伺服器傳送 HTTP 請求,包含請求頭和請求體。(4,5,6,7包含http頭部、響應碼、報文結構、cookie等知識)
- 伺服器接收並處理請求,並返回響應資料,包含狀態碼、響應頭和響應體。
- 瀏覽器接收到響應資料,解析響應頭和響應體,並根據狀態碼判斷是否成功。
- 如果響應成功,瀏覽器接收到http資料包後的解析流程(這部分涉及到html - 詞法分析,解析成DOM樹,解析CSS生成CSSOM樹(樣式樹),合併生成render渲染樹(樣式計算)。然後layout佈局,分層,呼叫GPU繪製等,最後將繪製的結果合成最終的頁面影像,顯示在螢幕上。這個過程會發生迴流和重繪)。
- 連線結束 -> 斷開TCP連線 四次揮手
梳理出主幹骨架,然後就需要往骨架上填充細節內容。
二、瀏覽器接收url並開啟一個新程序
這部分內容開始之前我們需要先透過一張圖對 程序 和 執行緒 的關係有一個初步的瞭解。
1. 瀏覽器是多程序的
瀏覽器是多程序的,有一個主程序,每開啟一個tab頁面都會新開一個程序(某些情況下多個tab會合並程序)。
注意:在這裡瀏覽器應該也有自己的最佳化機制,有時候開啟多個tab頁後,比如開啟多個空白標籤頁。可以在Chrome工作管理員中看到,程序被合併了。
程序可能包括主程序,外掛程序,GPU,tab頁(瀏覽器核心)等等。
- Browser程序:瀏覽器的主程序(負責協調、主控),只有一個。
- 第三方外掛程序:每種型別的外掛對應一個程序,僅當使用該外掛時才建立。
- GPU程序:最多一個,用於3D繪製等。
- 瀏覽器渲染程序(瀏覽器核心)(內部是多執行緒的):預設每個Tab頁面一個程序,互不影響。作用是頁面渲染,指令碼執行,事件處理等。(瀏覽器有時候會最佳化,如多個空白頁合併成一個程序)
強化記憶:在瀏覽器中開啟一個網頁相當於新起了一個程序(程序內有自己的多執行緒)
下圖以 chrome瀏覽器 為例。我們可以自己透過Chrome的更多工具 =》 工作管理員 自行驗證檢視,可以看到chrome的工作管理員中有多個程序(分別是每一個Tab頁面有一個獨立的程序,以及一個主程序) 然後能看到每個程序的記憶體資源資訊以及cpu佔有率。
2. 瀏覽器核心是多執行緒的
每一個tab頁面可以看作是瀏覽器核心的一個程序,然後這個程序是多執行緒的,它有幾大類子執行緒
- GUI渲染執行緒:負責渲染瀏覽器介面,解析HTML,CSS,構建DOM樹和RenderObject樹,佈局和繪製等。
GUI渲染執行緒與JS引擎執行緒是互斥的
。 - JS引擎執行緒:也叫 JS 核心,負責解析執行 JS 指令碼程式的主執行緒,例如 V8 引擎。JS引擎一直等待著任務佇列中任務的到來,然後加以處理,一個Tab頁(renderer程序)中無論什麼時候都只有一個JS執行緒在執行JS程式。
- 事件觸發執行緒:屬於瀏覽器核心執行緒,主要用於控制事件,例如滑鼠、鍵盤等,當事件被觸發時,就會把事件的處理函式推進事件佇列,等待 JS 引擎執行緒執行。
- 定時器觸發執行緒:主要控制 setInterval和 setTimeout,用來計時,計時完畢後,則把定時器的處理函式推進事件佇列中,等待 JS 引擎執行緒。
- 非同步http請求執行緒:透過XMLHttpRequest連線後,透過瀏覽器新開的一個執行緒,監控readyState狀態變更時,如果設定了該狀態的回撥函式,則將該狀態的處理函式推進事件佇列中,等待JS引擎執行緒執行。
![](data:image/svg+xml;utf8,)
可以看到,裡面的JS引擎是核心程序中的一個執行緒,這也是為什麼常說JS引擎是單執行緒的。
雖然 JS 是單執行緒的,但實際上參與工作的執行緒一共有四個:
後面三個只是協助,只有 JS 引擎執行緒是真正執行的。
3. JS引擎單執行緒的原因
JS引擎之所以是單執行緒,是由於JavaScript最初是作為瀏覽器指令碼語言開發的,並且JavaScript需要操作DOM等瀏覽器的API,如果多個執行緒同時進行DOM更新等操作則可能會出現各種問題(如競態條件、資料難以同步、複雜的鎖邏輯等),因此將JS引擎設計成單執行緒的形式就可以避免這些問題。 雖然JS引擎是單執行緒的,但是透過使用 非同步程式設計模型 和 事件迴圈機制,JS仍然可以實現高併發處理。
如果JS是多執行緒的場景描述: 那麼現在有2個執行緒,process1 process2,由於是多執行緒的JS,所以他們對同一個dom,同時進行操作 process1 刪除了該dom,而process2 編輯了該dom,同時下達2個矛盾的命令,瀏覽器究竟該如何執行呢?這時可能就會出現問題了。
4. GUI渲染執行緒與JS引擎執行緒互斥
由於JavaScript是可操縱DOM的,如果在修改這些元素屬性同時渲染介面(即JS執行緒和UI執行緒同時執行),那麼渲染執行緒前後獲得的元素資料就可能不一致了。
因此為了防止渲染出現不可預期的結果,瀏覽器設定GUI渲染執行緒與JS引擎為互斥的關係,當JS引擎執行時GUI執行緒會被掛起, GUI更新則會被儲存在一個佇列中等到JS引擎執行緒空閒時立即被執行。
因為本文主要講輸入URL後頁面的渲染過程,所以關於瀏覽器開啟網路請求執行緒這部分詳細內容大家可以移步檢視,裡面包括JS執行機制,程序執行緒的詳解: 從瀏覽器多程序到JS單執行緒,JS執行機制最全面的一次梳理
5. 渲染過程中遇到 JS 檔案如何處理?
JS的載入、解析與執行會阻塞文件的解析,也就是說,在構建 DOM 時,HTML 解析器若遇到了 JavaScript,那麼它會暫停文件的解析,將控制權移交給 JS 引擎,等 JS 引擎執行完畢,瀏覽器再從中斷的地方恢復繼續解析文件。 也就是說,如果想要首屏渲染的越快,就越不應該在首屏就載入 JS 檔案,這也是建議將 script 標籤放在 body 標籤底部的原因。當然在當下,並不是說 script 標籤必須放在底部,因為你可以給 script
標籤新增 defer
或者 async
屬性。
defer與async的區別
- defer要等到整個頁面在記憶體中正常渲染結束(DOM 結構完全生成,以及其他指令碼執行完成),才會執行。
- async一旦下載完,渲染引擎就會中斷渲染,執行這個指令碼以後,再繼續渲染。
- 一句話,
defer
是“渲染完再執行
”,async
是“下載完就執行
”。 - 另外,如果有多個defer指令碼,會按照它們在頁面出現的順序載入,而多個async指令碼是不能保證載入順序的。(因為只要該模組載入完成,就執行該模組,不確定模組什麼時候能載入完)
關於defer/async用法解釋
二、解析URL
輸入URL後,會進行解析(URL的本質就是統一資源定位符)
URL一般包括幾大部分:
- 協議(Protocol):指訪問資源時使用的協議,常見的協議有 HTTP、HTTPS、FTP 等。
- 主機名(Host):指伺服器的域名或 IP 地址,用於唯一標識一個伺服器。
- 埠號(Port):指伺服器上提供服務的埠號,可以省略。例如,預設的 HTTP 埠為 80,HTTPS 埠為 443。
- 路徑(Path):指伺服器上資源的路徑,表示訪問資源時需要進入的目錄層級以及資源的名稱。
- 查詢引數(Query):指對資源請求的引數,格式為 key=value,多個引數間使用
&
連線。 - 錨點(Fragment):指
#
後的hash值,一般用來定位到某個位置。
舉個例子,www.example.com/index.html?… 表示了一個 URL, 其中協議為 HTTP,主機名為 www.example.com,路徑為 /index.html,查詢引數為 key1=value1 和 key2=value2,錨點為 section。
三、DNS域名解析
在解析過程之前我們先理解幾個概念。
1. DNS是什麼?
DNS(Domain Name System)是一種用於將域名
解析為IP地址
的系統。(把我們的域名對映為IP地址,這就是DNS的作用) 它可以將人們易於記憶的域名轉換為伺服器可識別的IP地址,這樣使用者就可以使用域名訪問網站,而不必直接輸入數字格式的IP地址。
在瀏覽器中輸入網址時,電腦會先向DNS伺服器
傳送請求,獲取該網址對應的IP地址
,並在成功獲取後直接連線該IP地址對應的伺服器,在伺服器端獲取網頁內容並顯示出來,完成整個訪問過程。因此,DNS在網際網路中起著至關重要的作用。
2. IP和域名的關係
IP(Internet Protocol)地址是一個數字標識,用於唯一識別連線到網際網路上的每個計算機、伺服器和其他裝置。域名則是網站的人類可讀的名稱。域名系統(DNS伺服器)可以將域名轉換為與之關聯的IP地址。 簡單來說,IP地址是網路裝置的識別符號,而域名則是方便人們記憶和使用的網路地址別名。 域名系統透過將 域名
對映到 IP地址
,使網際網路上的使用者能夠以易記的方式訪問特定的網站或伺服器。
3. 域名伺服器概念圖
從上面這張圖可以看到,域名的管理是分層次的。最高階是根,也叫做根伺服器
。從上往下功能逐漸細化。DNS就是和這些伺服器進行打交道。 有了上面的這些概念,現在我們再來認識一下DNS域名解析過程就容易多了。
4. DNS域名解析過程
- 首先會在瀏覽器快取中查詢是否有該域名對應的IP地址,若有則直接返回,解析過程結束。
- 如果瀏覽器快取中沒有該域名對應的IP地址,則向本地DNS伺服器傳送查詢請求。
- 如果本地DNS伺服器快取中有該域名對應的IP地址,則直接返回,解析過程結束。
- 如果本地DNS伺服器快取中沒有該域名對應的IP地址,則向根域名伺服器傳送查詢請求。
- 根域名伺服器返回一個所查詢域的頂級域名伺服器地址。
- 本地DNS伺服器向 頂級域名伺服器 傳送查詢請求。
- 頂級域名伺服器返回下一級DNS伺服器的地址(權威DNS伺服器)。
- 本地DNS伺服器向權威DNS伺服器傳送查詢請求。
- 權威DNS伺服器返回該域名對應的IP地址,並將結果返回給本地DNS伺服器。
- 本地DNS伺服器將結果儲存在快取中,便於下次使用。並將結果返回給瀏覽器。
- 瀏覽器將結果儲存在快取中,並使用該IP地址訪問對應的網站。
這個過程大體大體由一張圖可以表示:從網上找的圖片方便理解。 而且,需要知道dns解析是很耗時的,因此如果解析域名過多,會讓首屏載入變得過慢,可以考慮dns-prefetch
最佳化
關於 本地DNS伺服器 這裡單獨講解下:
如果之前的過程無法解析時,作業系統會把這個域名傳送給這個本地DNS伺服器。每個完整的內網通常都會配置本地DNS伺服器,例如使用者是在學校或工作單位接入網際網路,那麼使用者的本地DNS伺服器肯定在學校或工作單位裡面。它們一般都會快取域名解析結果,當然快取時間是受到域名的失效時間控制的。大約80%的域名解析到這裡就結束了,後續的DNS迭代和遞迴也是由本地DNS伺服器負責。
5. DNS解析時發現域名和IP不一致,訪問了該域名會如何?
- 域名和IP不一致,域名解析成了其他的的IP地址,但是這個IP地址正確。訪問該域名就會訪問其他的網站。
知乎上有一個阿里巴巴的回答: 從技術上來講是可以解析到任意IP地址的,這時候針對這個地址發起HTTP訪問,HTTP頭中的host欄位會是你的域名(而非該IP對應站點的域名),如果對方的網站HTTP伺服器沒有做對應的防護就可以訪問,如果對方的網站HTTP伺服器有防護則無法訪問。
- 域名和IP不一致,域名解析成了其他的的IP地址,但是這個IP地址錯誤,訪問該域名就會失敗。
可參考:DNS解析時發現域名和IP不一致,訪問了該域名會如何(大廠真題)
四、建立 TCP 連線
需要了解3次握手規則建立連線以及斷開連線時的四次揮手。
拿到了IP地址後,就可以發起HTTP請求了。HTTP請求的本質就是TCP/IP的請求構建。建立連線時需要 3次握手 進行驗證,斷開連結也同樣需要 4次揮手 進行驗證,保證傳輸的可靠性。
1. 三次握手
模擬三次握手(場景對話版):
客戶端:hello,你是server麼? 服務端:hello,我是server,你是client麼 客戶端:yes,我是client
可透過下方圖文結合方式字理解三次握手:
三次握手原理:
第一次握手:客戶端傳送一個帶有 SYN
(synchronize同步)標誌的資料包給服務端。 第二次握手:服務端接收成功後,回傳一個帶有 SYN/ACK
標誌的資料包傳遞確認資訊,表示我收到了。 第三次握手:客戶端再回傳一個帶有 ACK
標誌的資料包,表示我知道了,握手結束。
其中:SYN標誌位數置1,表示建立TCP連線;ACK表示響應,置1時表示響應確認。
三次握手過程詳細說明: 剛開始客戶端處於 Closed
的狀態,服務端處於 Listen
狀態。
- 第一次握手: 客戶端傳送標識位SYN = 1,隨機產生序列號seq = x的資料包到服務端,服務端由SYN = 1知道客戶端要建立連線,並進入
SYN_SENT
狀態,等待伺服器確認;(SYN=1,seq=x,x為隨機生成的數值)
- 第二次握手: 伺服器收到請求並確認聯機資訊後,向客戶端傳送標識位SYN = 1,ACK = 1和隨機產生的序列號seq = y, 確認碼ack number = x+1(客戶端傳送的seq+1)的資料包,此時伺服器進入
SYN_RCVD
狀態;(SYN=1,ACK=1,seq=y,y為隨機生成的數值,確認號 ack=x+1)
這裡ack加1可以理解為時確認和誰建立連線。 - 第三次握手:客戶端收到後檢查確認碼ack number是否正確,即和第一次握手傳送的序列號加1結果是否相等,以及ACK標識位是否為1;若正確,客戶端傳送標識位ACK = 1、seq = x + 1和確認碼ack = y + 1(伺服器傳送的seq+1)到伺服器,伺服器收到後確認ACK=1和seq是否正確,若正確則完成建立連線,此包傳送完畢,客戶端和伺服器進入
ESTAB_LISHED
狀態。完成三次握手,客戶端與伺服器開始傳送資料.。(ACK=1,seq=x+1,ack=y+1)
TCP 三次握手的建立連線的過程就是相互確認初始序號的過程。告訴對方,什麼樣序號的報文段能夠被正確接收。 第三次握手的作用是: 客戶端對伺服器端的初始序列號的確認,如果只使用兩次握手,那麼伺服器就沒有辦法知道自己的序號是否已被確認。同時這樣也是為了防止失效的請求報文被伺服器接收,而出現錯誤的情況。
2. 四次揮手
模擬四次揮手(場景對話版):
主動方:我已經關閉了向你那邊的主動通道了,只能被動接收了 被動方:收到通道關閉的資訊,我這還有資料沒有傳送完成,你等下 被動方:那我也告訴你,我這邊向你的主動通道也關閉了 主動方:最後收到資料,之後雙方無法通訊
可透過下方圖文結合方式理解四次揮手:
四次揮手原理:
第一次揮手:客戶端傳送一個FIN,用來關閉客戶端到伺服器的資料傳送,並且指定一個序列號。客戶端進入FIN_WAIT_1
狀態。 第二次揮手:伺服器收到FIN後,傳送一個ACK給客戶端,確認序號為客戶端的序列號值 +1 ,表明已經收到客戶端的報文了,此時伺服器處於 CLOSE_WAIT
狀態。 第三次揮手:伺服器傳送一個FIN,用來關閉伺服器到客戶端的資料傳送,伺服器進入LAST_ACK
狀態。 第四次揮手:客戶端收到FIN後,客戶端進入TIME_WAIT
狀態,接著傳送一個ACK給伺服器,確認序號為收到序號+1 ,伺服器收到確認後進入CLOSED
狀態,完成四次揮手。
其中:FIN標誌位數置1,表示斷開TCP連線。
四次揮手過程詳細說明:
剛開始雙方都處於 ESTABLISHED
狀態,假如是客戶端先發起關閉請求。
- 第一次揮手:客戶端傳送一個FIN = 1、初始化序列號seq = u,到伺服器,表示需要斷開TCP連線,客戶端進入
FIN_WAIT_1
狀態,等待伺服器的確認。(FIN = 1,seq = u,u由客戶端隨機生成)
- 第二次揮手:伺服器收到這個FIN,它發回ACK = 1、seq序列號(由回覆端隨機生成)、確認序號ack為收到的序號加1(ack = u+1);以便客戶端收到資訊時,知曉自己的TCP斷開請求已經得到驗證。伺服器進入
CLOSE_WAIT
,等待關閉連線;客戶端進入FIN_WAIT_2
,稍後關閉連線。(ACK = 1,seq = v,ack = u+1)
- 第三次揮手:伺服器在回覆完客戶端的TCP斷開請求後,不會馬上進行TCP連線的斷開。伺服器會先確保斷開前,所有傳輸到客戶端的資料是否已經傳輸完畢,一旦確認傳輸完畢,就會發回FIN = 1,ACK = 1,seq = w和確認碼ack = u+1給客戶端,伺服器進入
LAST_ACK
狀態,等待最後一次ACK確認;(FIN = 1,ACK = 1,seq = w,ack = u+1 ,w由伺服器端隨機生成)
- 第四次揮手:客戶端收到伺服器的TCP斷開請求後,會回覆伺服器的斷開請求。包含ACK = 1、隨機生成的seq = u+1,並將確認序號設定為收到序號加1(ack = w+1)到伺服器,從而完成伺服器請求的驗證回覆。客戶端進入
TIME-WAIT
狀態,此時 TCP 未釋放掉,需要等待2MSL
以確保伺服器收到自己的 ACK 報文後進入CLOSE
狀態,服務端進入CLOSE
狀態。(ACK = 1,seq = u+1,ack = w+1)
注意:為什麼 TIME_WAIT 等待的時間是 2MSL? 1)MSL 是 報文最大生存時間,一來一回需要等待 2 倍的時間。 2)最後一次揮手中,客戶端會等待一段時間再關閉的原因,是為了防止傳送給伺服器的確認報文段丟失或者出錯,從而導致伺服器 端不能正常關閉。
常用關鍵詞總結:
- SYN標誌位用來建立TCP連線。如果SYN=1而ACK=0,表明它是一個連線請求;如果SYN=1且ACK=1,則表示同意建立一個連線。
- ACK表示響應,置1時表示確認號(為合法,為0的時候表示資料段不包含確認資訊,確認號被忽略。)
- FIN表示關閉連線,置1時表示發端完成傳送任務。用來釋放連線,表明傳送方已經沒有資料傳送了。
為什麼需要四次揮手呢?
- TCP協議 的連線是
全雙工
的,即資料傳輸可以同時在兩個方向上進行。所以終止連線時,需要每個方向都單獨關閉。(單獨一方的連線關閉,只代表不能再向對方傳送資料,連線處於的是半關閉狀態) - 客戶端傳送FIN報文終止連線後,
伺服器可能還有資料需要傳送
(比如上一次的響應),所以伺服器會先傳送ACK報文確認收到FIN報文,並將未傳送的資料傳送出去,然後再傳送自己的FIN報文終止連線。 - 客戶端接收到伺服器的FIN報文後也需要傳送ACK報文確認收到,才能正式關閉連線。
3. 為什麼是三次握手?不是兩次、四次?
為了確認雙方的 接收能力 和 傳送能力 都正常。
如果是用兩次握手,則會出現下面這種情況: 如客戶端發出連線請求,但因連線請求報文丟失而未收到確認,於是客戶端再重傳一次連線請求。後來收到了確認,建立了連線。資料傳輸完畢後,就釋放了連線,此時客戶端共發出了兩個連線請求報文段。 其中第一個丟失,第二個到達了服務端,但是第一個丟失的報文段只是在某些網路節點長時間滯留了,延誤到連線釋放以後的某個時間才到達服務端,此時服務端誤以為客戶端又發出一次新的連線請求,於是就向客戶端發出確認報文段,同意建立連線,不採用三次握手;只要服務端發出確認,就建立新的連線了。此時客戶端忽略服務端發來的確認,也不傳送資料,則服務端一直等待客戶端傳送資料,浪費了資源。
4. TCP/IP的分層管理
按層次分為以下四層:應用層
、傳輸層
、網路層
和資料鏈路層
。
為什麼要分層呢?
分層是有一定好處的。比如,如果網際網路是由一個協議統籌,某個地方需要改變設計時,就必須把所有部分整體替換掉。而分層之後只需把變動的層替換掉即可。把各層之間的介面部分規劃好之後,每個層次內部的設計就能夠自由改動了。
各層的作用:
- 應用層(DNS,HTTP協議)DNS將域名解析成IP地址併傳送HTTP請求,OSI 參考模型中最靠近使用者的一層。
- 傳輸層(TCP,UDP) 建立TCP連線(三次握手),客戶端和服務端資料傳輸就是在這層進行的。
- 網路層(IP,ARP地址解析協議)IP定址及路由選擇;所起的作用就是在眾多的選項內選擇一條傳輸線路。
- 資料鏈路層:用來處理連線網路的硬體部分,硬體上的範疇均在鏈路層的作用範圍之內。
其實就是一個概念:從客戶端發出HTTP請求到伺服器接收,中間會經過一系列的流程。
簡括就是:
從應用層 DNS 將域名解析成 IP 地址,併傳送 HTTP 請求,到傳輸層透過三次握手建立tcp/ip連線,再到網路層的ip定址,再到資料鏈路層的封裝成幀,利用物理介質傳輸。
當然,服務端的接收就是反過來的步驟。傳送端從應用層往下走,接收端則從鏈路層往上走。
舉例,其實分層這部分大致瞭解下,知道怎麼回事就可以啦。
我們用HTTP舉例來說明,首先作為傳送端的客戶端在應用層(HTTP協議)發出某個想看Web頁面的HTTP請求。 接著,為了傳輸方便,在傳輸層(TCP協議)把應用層處收到的資料(HTTP請求報文)進行分割,並在各個報文上打上標記序號及埠號轉發給網路層。 在網路層(IP協議),增加作為通訊目的地的MAC地址後轉發給鏈路層。這樣一來,發往網路的通訊請求就準備齊全了。 接收端的伺服器在鏈路層接收到資料,瞬狙往上層傳送,一直到應用層。當傳輸到應用層,才算真正接收到由客戶端傳送過來的HTTP請求。
五、瀏覽器向伺服器傳送 HTTP 請求
1. HTTP請求報文都有什麼組成?
HTTP請求報文主要由三個部分組成:請求行
、請求頭
和請求體
。具體如下:
請求行:包含請求方法
、URI(請求的資源路徑)
和HTTP協議版本
。例如:GET /index.html HTTP/1.1。 請求頭(Header): 包含了客戶端向伺服器傳送的附加資訊,例如瀏覽器型別、字元編碼、認證資訊等。請求頭以鍵值對
的形式存在,多個鍵值對之間以換行符分隔。例如:Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7。 請求體(Body) : 存放請求引數
,即瀏覽器向伺服器傳輸資料的實體部分。常用於POST方法提交請求時,傳送表單資料、JSON資料等型別的資料。
需要注意的是,並不是所有的HTTP請求都必須帶有請求體,像GET請求
通常不需要傳送請求體。
為什麼 HTTP 報文中要存在 “空行”? 因為 HTTP 協議並沒有規定報頭部分的鍵值對有多少個。空行就相當於是 “報頭的結束標記”, 或者是 “報頭和正文之間的分隔符”。 HTTP 在傳輸層依賴 TCP 協議, TCP 是面向位元組流的. 如果沒有這個空行, 就會出現 “粘包問題”
2. 常見狀態碼含義
區分狀態碼 1××開頭 - 資訊性狀態碼,表示HTTP請求已被接收,需要進一步處理。 2××開頭 - 成功狀態碼,表示請求已成功處理完成。 3××開頭 - 重定向狀態碼,表示請求需要進一步的操作以完成。 4××開頭 - 客戶端錯誤狀態碼,表示請求包含錯誤或無法完成。 5××開頭 - 伺服器錯誤狀態碼,表示伺服器無法完成有效的請求。
常見狀態碼 200 - 請求成功,從客戶端傳送給伺服器的請求被正常處理並返回
301 - 表示被請求的資源已經被永久移動到新的URI(永久重定向) 302 - 表示被請求的資源已經被臨時移動到新的URI(臨時重定向) 304 - 表示伺服器資源未被修改;通常是在客戶端發出了一個條件請求,伺服器透過比較資源的修改時間來確定資源是否已被修改
400 - 伺服器不理解請求,請求報文中存在語法錯誤 401 - 請求需要身份驗證 403 - 伺服器拒絕請求(訪問許可權出現問題) 404 - 被請求的資源不存在 405 - 不允許的HTTP請求方法,意味著正在使用的HTTP請求方法不被伺服器允許
500 - 伺服器內部錯誤,無法完成請求 503 - 伺服器當前無法處理請求,一般是因為過載或維護
3. 請求/響應頭部
請求和響應頭部也是分析時常用到的。
常用的請求頭部(部分):
Accept: 接收型別,表示瀏覽器支援的MIME型別 (對標服務端返回的
Content-Type
) Accept-Encoding:瀏覽器支援的壓縮型別,如gzip
等,超出型別不能接收 Content-Type:客戶端傳送出去實體內容的型別 Cache-Control: 指定請求和響應遵循的快取機制,如no-cache
If-Modified-Since:對應服務端的Last-Modified
,用來匹配看檔案是否變動,只能精確到1s之內,http1.0
中 Expires:快取控制,在這個時間內不會請求,直接使用快取,http1.0,而且是服務端時間 Max-age:代表資源在本地快取多少秒,有效時間內不會請求,而是使用快取,http1.1中 If-None-Match:對應服務端的ETag
,用來匹配檔案內容是否改變(非常精確),http1.1中 Cookie: 有cookie
並且同域訪問時會自動帶上 Connection: 當瀏覽器與伺服器通訊時對於長連線如何進行處理,如keep-alive
Host:請求的伺服器URL
Origin:最初的請求是從哪裡發起的(只會精確到埠),Origin
比Referer
更尊重隱私 Referer:該頁面的來源URL
(適用於所有型別的請求,會精確到詳細頁面地址,csrf
攔截常用到這個欄位) User-Agent:使用者客戶端的一些必要資訊,如UA頭部等
常用的響應頭部(部分):
Access-Control-Allow-Headers: 伺服器端允許的請求
Headers
Access-Control-Allow-Methods: 伺服器端允許的請求方法 Access-Control-Allow-Origin: 伺服器端允許的請求Origin
頭部(譬如為*) Content-Type:服務端返回的實體內容的型別 Date:資料從伺服器傳送的時間 Cache-Control:告訴瀏覽器或其他客戶,什麼環境可以安全的快取文件 Last-Modified:請求資源的最後修改時間 Expires:應該在什麼時候認為文件已經過期,從而不再快取它 Max-age:客戶端的本地資源應該快取多少秒,開啟了Cache-Control
後有效 ETag:請求變數的實體標籤的當前值 Set-Cookie:設定和頁面關聯的cookie
,伺服器透過這個頭部把cookie
傳給客戶端 Keep-Alive:如果客戶端有keep-alive
,服務端也會有響應(如timeout=38) Server:伺服器的一些相關資訊
一般來說,請求頭部和響應頭部是匹配分析的。 譬如,請求頭部的Accept
要和響應頭部的Content-Type
匹配,否則會報錯。 譬如,跨域請求時,請求頭部的Origin
要匹配響應頭部的Access-Control-Allow-Origin
,否則會報跨域錯誤。 譬如,在使用快取時,請求頭部的If-Modified-Since
、If-None-Match
分別和響應頭部的Last-Modified
、ETag
對應。
注意點
:
請求頭 和 響應頭 中的 Content-Type
,是不一樣的。
請求頭的Content-Type常見取值:
javascript複製程式碼application/x-www-from-urlencoded //以鍵值對的資料格式提交
multipart/form-data //用於上傳檔案圖片等二進位制資料
響應頭的Content-Type常見取值:
javascript複製程式碼text/html // body 資料格式是 HTML
text/css // body 資料格式是 CSS
application/javascript // body 資料格式是 JavaScript
application/json //body 資料格式是 JSON (最常見的)
4. 請求/響應體
http 請求 時,除了頭部,還有訊息實體
,一般來說, 請求實體中會將一些需要的引數都放入(用於post
請求)。 比如實體中可以放引數的序列化形式(a=1&b=2
這種),或者直接放表單物件(Form Data
物件,上傳時可以夾雜引數以及檔案)等等。
而一般 響應實體中,就是放服務端需要返給客戶端的內容。 一般現在的介面請求時,實體中就是資訊的json格式,而像頁面請求這種,裡面直接放了一個html字串,然後瀏覽器自己解析並渲染。
如下圖所示(post請求傳送給介面的資料)
注意點
:
- 不是所有的HTTP請求都必須帶有請求體,像
GET請求
通常不需要傳送請求體。 - 響應完成之後怎麼辦?TCP 連線就斷開了嗎?
不一定。這時候要判斷Connection欄位, 如果請求頭或響應頭中包含
Connection: Keep-Alive
, 表示建立了持久連線
,這樣TCP連線會一直保持,之後請求統一站點的資源會複用這個連線。否則斷開TCP連線, 請求-響應流程結束。
5. cookie以及最佳化
cookie是瀏覽器的一種本地儲存方式,一般用來幫助 客戶端 和 服務端 通訊的,常用來進行身份校驗,結合服務端的 session 使用。
場景如下(簡述):
在登陸頁面,使用者登陸了 此時,服務端會生成一個
session
,session
中有對應使用者的資訊(如使用者名稱、密碼等) 然後會有一個sessionid
(相當於是服務端的這個session對應的key) 然後服務端在登入頁面中寫入cookie
,值就是: jsessionid=xxx 然後瀏覽器本地就有這個cookie
了,以後訪問同域名
下的頁面時,自動帶上cookie,自動檢驗,在有效時間內無需二次登陸。
一般來說,cookie是不允許存放敏感資訊的(千萬不要明文儲存使用者名稱、密碼),因為非常不安全,如果一定要強行儲存,首先,一定要在cookie中設定httponly
(這樣就無法透過js操作了),另外可以考慮RSA等非對稱加密(因為實際上,瀏覽器本地也是容易被攻克的,並不安全) 另外,由於在同域名的資源請求時,瀏覽器會預設帶上本地的cookie,針對這種情況,在某些場景下是需要最佳化的。
比如以下場景:
客戶端在 域名A 下有cookie(這個可以是登入時由服務端寫入的) 然後在 域名A 下有一個頁面,頁面中有很多依賴的靜態資源(都是 域名A 的,譬如有20個靜態資源) 此時就有一個問題,頁面載入,請求這些靜態資源時,瀏覽器會預設帶上 cookie 也就是說,這20個靜態資源的 http請求,每一個都得帶上 cookie,而實際上靜態資源並不需要 cookie 驗證 此時就造成了較為嚴重的浪費,而且也降低了訪問速度(因為內容更多了)
針對這種場景,是有最佳化方案的(多域名拆分
)。具體做法就是:
- 將靜態資源分組,分別放到不同的域名下(如
static.base.com
) - 而
page.base.com
(頁面所在域名)下請求時,是不會帶上static.base.com
域名的cookie的,所以就避免了浪費
說到了多域名拆分,這裡再提一個問題,那就是:
- 在移動端,如果請求的域名數過多,會降低請求速度(因為域名整套解析流程是很耗費時間的,而且移動端一般頻寬都比不上pc)
- 此時就需要用到一種最佳化方案:
dns-prefetch
(讓瀏覽器空閒時提前解析dns域名,不過也請合理使用,勿濫用)
關於cookie的互動,可以看下圖總結
6. HTTP協議各版本的區別
HTTP協議的版本歷經多次更新迭代,主要包括 HTTP/1.0
、HTTP/1.1
和HTTP/2
等版本,它們之間的主要區別如下:
1)HTTP/1.0:
- 瀏覽器與伺服器只保持
短連線
,瀏覽器的每次請求都需要與伺服器建立一個TCP連線,都要經過三次握手,四次揮手。而且是序列請求。 - 由於瀏覽器必須等待響應完成才能發起下一個請求,造成
“隊頭阻塞”
。 如果某請求一直不到達,那麼下一個請求就一直不傳送。(高延遲–帶來頁面載入速度的降低)
2)HTTP/1.1:目前使用最廣泛的版本
- 支援
長連線
,透過Connection: keep-alive
保持HTTP連線不斷開,避免重複建立TCP連線。 - 支援
管道化傳輸
,透過長連線實現一個TCP連線中同時處理多個HTTP請求;只要第一個請求發出去了,不必等其回來,就可以發第二個請求出去,可以減少整體的響應時間。 伺服器會按照請求的順序去返回響應的內容,無法存在並行響應。(http請求返回順序按照伺服器響應速度來排序,這裡也會引入promise.then 和 async await 來控制介面請求順序) - 新增了一些請求方法,新增了一些請求頭和響應頭(如下)
- 支援
斷點續傳
, 新增 Range 和 Content-Range 頭表示請求和響應的部分內容 - 加入快取處理(響應頭新欄位Expires、Cache-Control)
- 增加了重要的頭
Host
欄位;為了支援多虛擬主機的場景,使用同一個IP地址上可以託管多個域名,訪問的都是同一個伺服器,從而滿足HTTP協議發展所需要的更高階的特性。 - 並且新增了其他請求方法:put、delete、options…
缺點:
- 隊頭阻塞
- 無狀態通訊模型(巨大的HTTP頭部),也就是伺服器端不儲存客戶端請求的任何狀態資訊。這樣會造成一些需求頻繁互動的應用程式難以實現,需要透過其他機制來保證狀態的一致性等。
- 明文傳輸–不安全
- 不支援服務端推送
3)HTTP/2.0:
- 採用
二進位制格式
而非文字格式 多路複用
,在同一個TCP連線上同時傳輸多條訊息;每個請求和響應都被分配了唯一的識別符號,稱為“流(Stream)”,這樣每條資訊就可以獨立地在網路上傳輸。- 使用 HPACK 演算法
報頭壓縮
,降低開銷。 伺服器推送
,支援伺服器主動將相關資源預測性地推送給客戶端,以減少後續的請求和延遲。(例如 HTML、CSS、JavaScript、影像和影片等檔案)
4)HTTP3.0
是 HTTP/3 中的底層支撐協議,該協議基於 UDP,又取了 TCP 中的精華,實現了即快又可靠的協議。
- 運輸層由TCP改成使用UDP傳輸
- 隊頭堵塞問題的解決更為徹底
- 切換網路時的連線保持:基於TCP的協議,由於切換網路之後,IP會改變,因而之前的連線不可能繼續保持。而基於UDP的QUIC協議,則可以內建與TCP中不同的連線標識方法,從而在網路完成切換之後,恢復之前與伺服器的連線
- 升級新的壓縮演算法
注意: HTTP 1.1起支援長連線,keep-alive不會永遠保持,它有一個持續時間,一般在伺服器中配置(如apache),另外長連線需要客戶端和伺服器都支援時才有效。
管道傳輸和多路複用的區別
HTTP/2 的多路複用可以理解為一條公路上同時行駛多輛車的場景,每輛車對應一個請求或響應,而公路對應一個 TCP 連線。 在 HTTP/1.x 中,只能一輛車(請求或響應)透過這條公路,其他車必須等待前面的車透過後再行駛; 而在 HTTP/2 中,則允許多輛車同時在這條公路上行駛,它們之間不會互相干擾或阻塞,從而提高了公路的使用效率和通行能力。
關於HTTP協議這部分感興趣的可以看 HTTP的前世今生
六、單獨拎出來的快取問題,HTTP快取策略
瀏覽器快取的特點:
- 瀏覽器每次發起請求,都會
先在瀏覽器快取中查詢該請求的結果以及快取標識
- 瀏覽器每次拿到返回的請求結果都會
將該結果和快取標識存入瀏覽器快取中
根據是否需要向伺服器重新發起HTTP請求將快取過程分為兩個部分,分別是強快取
和協商快取
。
- 強快取:使用強快取策略時,如果快取資源在
過期時間
內,是的話直接從本地快取中讀取資源,不與伺服器進行通訊。常見的快取控制欄位有Expires
和Cache-Control
。注意,如果同時啟用了Cache-Control與Expires,Cache-Control優先順序高。 - 協商快取:如果強快取失效後,客戶端將向伺服器發出請求,進行協商快取。瀏覽器攜帶上一次請求返回的響應頭中的 快取標識 向伺服器發起請求(如ETag、Last-Modified等),由伺服器判斷資源是否更新。如果
資源沒有更新
,則返回狀態碼304
Not Modified,告訴瀏覽器可以使用本地快取;否則返回新的資源內容。強快取優先順序高於協商快取
,但是協商快取可以更加靈活地控制快取的有效性。
七、頁面渲染流程
1. 流程簡述
瀏覽器核心拿到內容後,渲染步驟大致可以分為以下幾步:
1)解析HTML: 解析
HTML
並構建DOM
樹。 2)解析CSS: 解析CSS
構建CSSOM
樹(樣式樹)。 3)合成渲染樹:將DOM
與CSSOM
合併成一個渲染樹
(Render Tree) 。 4)佈局計算:根據渲染樹的結構,計算每個節點
在螢幕上的大小
、位置
等屬性,生成佈局資訊(Layout)。這個過程會發生迴流和重繪。 5)繪製頁面:將生成的佈局資訊交給瀏覽器的繪圖引擎,透過GPU
加速將畫素繪製(Paint)到螢幕上。 6)瀏覽器迴流和重繪:如果頁面發生改變,瀏覽器需要重新計算佈局和繪製,這可能會導致效能問題。因此我們應儘量避免頻繁的 DOM 操作和調整元素樣式,以減少不必要的迴流和重繪。
2. 解析HTML,構建DOM樹
解析過程中遇到 CSS 解析 CSS,遇到 JS 執行 JS。為了提高解析效率,瀏覽器在開始解析前,會啟動一個預解析的執行緒,率先下載 HTML 中的外部 CSS 檔案和 外部的 JS 檔案。 如果主執行緒解析到
link
位置,此時外部的 CSS 檔案還沒有下載解析好,主執行緒不會等待,繼續解析後續的 HTML。這是因為下載和解析 CSS 的工作是在預解析執行緒中進行的。這就是 CSS 不會阻塞 HTML 解析的根本原因。 如果主執行緒解析到script
位置,會停止解析 HTML,轉而等待 JS 檔案下載好,並將全域性程式碼解析執行完成後,才能繼續解析 HTML。這是因為 JS 程式碼的執行過程可能會修改當前的 DOM 樹,所以 DOM 樹的生成必須暫停。這就是 JS 會阻塞 HTML 解析的根本原因。
瀏覽器會遵守一套步驟將 HTML 檔案轉換為 DOM 樹。宏觀上,可以分為幾個步驟:
瀏覽器從磁碟或網路讀取HTML的原始位元組,並根據檔案的指定編碼(例如UTF-8)將它們轉換成字串。
在網路中傳輸的內容其實都是0和1這些位元組資料。當瀏覽器接收到這些位元組資料以後,它會將這些資料轉換為字串,就是我們的程式碼。
比如假設有這樣一個HTML頁面:(以下部分的內容出自參考來源,修改了下格式)
html複製程式碼<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
</body>
</html>
瀏覽器的處理過程如下:(以下圖片出自參考來源)
注意點:
- 將字串轉換成Token,例如:
<html>
、<head>
、<body>
等。Token中會標識出當前Token是 “開始標籤” 或是 “結束標籤” 亦或是 “文字” 等資訊。 - 構建DOM的過程中,不是等所有Token都轉換完成後再去生成節點物件,而是一邊生成Token,一邊消耗Token來生成節點物件。換句話說,每個Token被生成後,會立刻消耗這個Token建立出節點物件。注意:帶有結束標籤標識的Token不會再去建立節點物件。
3. 解析CSS,構建CSSOM樹
構建 CSSOM 樹的過程與 構建DOM 的過程非常相似,當瀏覽器接收到一段CSS,瀏覽器首先要做的是識別出Token,然後 構建節點 並生成 CSSOM。簡述為:
這一過程中,CSS匹配HTML元素是一個相當複雜和有效能問題的事情,瀏覽器得遞迴CSSOM樹,確定每一個節點的樣式到底是什麼,所以DOM樹要小,CSS儘量用id和class,千萬不要過度層疊下去。
4. 合成渲染樹(樣式計算)
當我們生成 DOM 樹和 CSSOM 樹以後,就需要將這兩棵樹組合為 渲染樹。 (以下圖片出自參考來源)
在這一過程中,不是簡單的將兩者合併就行了。渲染樹只會包括需要顯示的節點和這些節點的樣式資訊,如果某個節點是display: none
的,那麼就不會在渲染樹中顯示。
主執行緒會遍歷得到的 DOM 樹,依次為樹中的每個節點計算出它最終的樣式,稱之為 Computed Style。 在這一過程中,很多預設值會變成絕對值,比如
red
會變成rgb(255,0,0)
;相對單位會變成絕對單位,比如em
會變成px
這一步完成後,會得到一棵帶有樣式的 DOM 樹。
5. 佈局
佈局完成後會生成佈局樹。 當瀏覽器生成渲染樹以後,就會根據渲染樹來進行佈局(也可以叫做 迴流
)。這一階段瀏覽器要做的事情是要弄清楚各個節點在頁面中的確切 位置和大小
。通常這一行為也被稱為“自動重排”。
佈局流程的輸出是一個“盒模型”,它會精確地捕獲每個元素在視口內的確切位置和尺寸,所有相對測量值都將轉換為螢幕上的絕對畫素。
佈局階段會依次遍歷 DOM 樹的每一個節點,計算每個節點的幾何資訊。例如節點的寬高、相對包含塊的位置。 大部分時候,DOM 樹和佈局樹並非一一對應。 比如
display:none
的節點沒有幾何資訊,因此不會生成到佈局樹;又比如使用了偽元素選擇器
,雖然 DOM 樹中不存在這些偽元素節點,但它們擁有幾何資訊,所以會生成到佈局樹中。還有匿名行盒、匿名塊盒等等都會導致 DOM 樹和佈局樹無法一一對應。
下圖採用網上老師的圖片展示一下,更容易理解。
6. 分層
主執行緒會使用一套複雜的策略對整個佈局樹中進行分層。 分層的好處在於,將來某一個層改變後,僅會對該層進行後續處理,從而提升效率。 捲軸、堆疊上下文、transform、opacity 等樣式都會或多或少的影響分層結果,也可以透過will-change
屬性更大程度的影響分層結果。
關於分層我們可以f12檢視layers這一項,沒有的話,就去瀏覽器更多工具裡開啟。
7. 繪製
主執行緒會為每個層單獨產生繪製指令集,用於描述這一層的內容該如何畫出來。 完成繪製後,主執行緒將每個圖層的繪製資訊提交給合成執行緒,剩餘工作將由合成執行緒完成。 合成執行緒首先對每個圖層進行分塊,將其劃分為更多的小區域。 它會從執行緒池中拿取多個執行緒來完成分塊工作。
下圖採用網上老師的圖片展示一下,更容易理解。
6. 瀏覽器迴流和重繪
(1)迴流 迴流 的本質就是重新計算 layout 樹
。 當進行了會影響佈局樹的操作後,需要重新計算佈局樹,會引發 layout。 為了避免連續的多次操作導致佈局樹反覆計算,瀏覽器會合並這些操作,當 JS 程式碼全部完成後再進行統一計算。所以,改動屬性造成的 迴流 是非同步
完成的。
也同樣因為如此,當 JS 獲取佈局屬性時,就可能造成無法獲取到最新的佈局資訊。 瀏覽器在反覆權衡下,最終決定獲取屬性(比如 dom.clientWidth)立即 迴流。
(2)重繪 重繪 的本質就是重新根據分層資訊計算了繪製
指令。 當改動了可見樣式後,就需要重新計算,會引發 重繪。 由於元素的佈局資訊也屬於可見樣式,所以 迴流 一定會引起 重繪。
(3)最後總結
- 迴流(也叫重排):當 DOM結構發生變化 或者 元素樣式 發生改變時,瀏覽器需要重新計算樣式和渲染樹,這個過程比較消耗效能。
- 重繪:指元素的外觀樣式發生變化(比如改變 背景色,邊框顏色,文字顏色color等 ),但是佈局沒有變,此時瀏覽器只需要應用新樣式繪製元素就可以了,比迴流消耗的效能小一些。
迴流必定會發生重繪,重繪卻可以單獨出現
。迴流的成本開銷要高於重繪,而且一個節點的迴流往往回導致子節點以及同級節點的迴流, 所以最佳化方案中一般都包括,儘量避免迴流。
總之,對元素執行迴流操作之後,還有可能引起重繪
或者合成
操作,形象地理解就是“牽一髮而動全身
”。
(4)什麼情況引起迴流?
- 頁面的首次渲染
- 瀏覽器的視窗大小發生變化
- 元素內容發生變化
- 元素的尺寸或位置發生變化
- 元素的字型大小發生變化
- 新增或刪除可見的DOM元素
- 啟用CSS偽類
- 查詢某些屬性或者呼叫某些方法
所以一般會有一些最佳化方案,如:
- 使用 CSS 動畫代替 JavaScript 動畫:CSS 動畫利用 GPU 加速,在效能方面通常比 JavaScript 動畫更高效。使用 CSS 的
transform
和opacity
屬性來建立動畫,而不是改變元素的佈局屬性,如寬度、高度等。 - 使用 translated3d 開啟硬體加速:將元素的位移屬性設定為 translated3d( 0,0,0 ),可以強制使用 GPU 加速。有助於避免迴流,並提高動畫流暢度。
- 避免頻繁操作影響佈局的樣式屬性:當需要對元素進行多次樣式修改時,可以考慮將這些修改合併為一次操作。透過新增/移除 css類來一次性改變多個樣式屬性,而不是逐個修改。
- 使用 requestAnimationFrame:透過使用 requestAnimationFrame 方法排程動畫幀,可以確保動畫在瀏覽器的重繪週期內執行,從而避免不必要的迴流。這種方式可確保動畫在最佳時間點進行渲染。
- 使用文件片段(Document Fragment) :當需要在 DOM 中插入大量新元素時,可以先將這些元素新增到文件片段中,然後再將整個文件片段一次性插入到 DOM 中。這樣可以減少迴流和重繪的次數。(vue 虛擬dom的做法)
- 使元素脫離文件流:
position: absolute
/position: fixed
/float:left
(只是減少迴流,不是避免迴流) - 使用 visibility:hidden 代替 display: none :visibility:hidden不會觸發迴流,因為元素仍然佔據空間,只是不可見。而 display: none 會將元素從渲染樹中移除,引起迴流。
注意:改變字型大小會引發迴流。
瀏覽器渲染小結: 整個過程如下:
DOM TREE(DOMContentLoaded事件觸發) => 「執行JS」沒完成會阻止接下來的渲染 => CSSOM TREE => RENDER TREE渲染樹「瀏覽器未來是按照這個樹來繪製頁面的」=> Layout佈局計算「迴流/重排」=> Painting繪製「重繪」
需要注意幾個事項:
1. CSSOM會阻塞渲染,只有當CSSOM構建完畢後才會進入下一個階段構建渲染樹。(這點與瀏覽器最佳化有關,防止css規則不斷改變,避免了重複的構建) 2. 通常情況下DOM和CSSOM是並行構建的,但是當瀏覽器遇到一個script標籤時,DOM構建將暫停,直至JS指令碼下載完成並執行後才會繼續解析HTML。因為 JavaScript 可以使用諸如 document.write() 更改整個 DOM 結構之類的東西來更改文件的形狀,因此 HTML 解析器必須等待 JavaScript 執行才能恢復HTML文件解析。 3. 如果你想首屏渲染的越快,就越不應該在首屏就載入 JS 檔案,建議將 script 標籤放在 body 標籤底部。 4. 如果主執行緒解析到
link
位置,此時外部的 CSS 檔案還沒有下載解析好,主執行緒不會等待,繼續解析後續的 HTML。這是因為下載和解析 CSS 的工作是在預解析執行緒中進行的。這就是 CSS 不會阻塞 HTML 解析的根本原因。
總結
本文的目的:梳理出自己的知識體系。
梳理出知識體系後,有了一個大致骨架,由於知識點是環環相扣的,後期也不容易遺忘。以後就算在這方面又學習了新的知識,有了這些基礎學起來也會事半功倍些。更重要的是容易舉一反三,可以由一個普通問題,深挖擴充到底層原理。
以後再有相關問題,也會繼續在這個骨架上填充細節。
可參考: 從輸入URL到頁面載入的過程?如何由一道題完善自己的前端知識體系! 超詳細講解頁面載入過程 瀏覽器渲染 前端知識體系整理 - 瀏覽器頁面載入過程
作者:鐵錘妹妹i 連結:https://juejin.cn/post/7316775422187061300 來源:稀土掘金 著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
本文轉自 https://zhuanlan.zhihu.com/p/689679647,如有侵權,請聯絡刪除。