輸入 URL 到頁面渲染的整個(gè)流程

DNS 解析

TCP 三次握手

發(fā)送請(qǐng)求,分析 url,設(shè)置請(qǐng)求報(bào)文(頭,主體)

服務(wù)器返回請(qǐng)求的文件 (html)

瀏覽器渲染

HTML parser --> DOM Tree

標(biāo)記化算法,進(jìn)行元素狀態(tài)的標(biāo)記

dom 樹構(gòu)建

CSS parser --> Style Tree

解析 css 代碼,生成樣式樹

attachment --> Render Tree

結(jié)合 dom樹 與 style樹,生成渲染樹

layout: 布局

GPU painting: 像素繪制頁面

DNS

DNS 的作用就是通過域名查詢到具體的 IP。

因?yàn)?IP 存在數(shù)字和英文的組合(IPv6),很不利于人類記憶,所以就出現(xiàn)了域名。你可以把域名看成是某個(gè) IP 的別名,DNS 就是去查詢這個(gè)別名的真正名稱是什么。

在 TCP 握手之前就已經(jīng)進(jìn)行了 DNS 查詢,這個(gè)查詢是操作系統(tǒng)自己做的。當(dāng)你在瀏覽器中想訪問 www.google.com 時(shí),會(huì)進(jìn)行一下操作:

操作系統(tǒng)會(huì)首先在本地緩存中查詢 IP
沒有的話會(huì)去系統(tǒng)配置的 DNS 服務(wù)器中查詢
如果這時(shí)候還沒得話,會(huì)直接去 DNS 根服務(wù)器查詢,這一步查詢會(huì)找出負(fù)責(zé) com 這個(gè)一級(jí)域名的服務(wù)器
然后去該服務(wù)器查詢 google 這個(gè)二級(jí)域名
接下來三級(jí)域名的查詢其實(shí)是我們配置的,你可以給 www 這個(gè)域名配置一個(gè) IP,然后還可以給別的三級(jí)域名配置一個(gè) IP
以上介紹的是 DNS 迭代查詢,還有種是遞歸查詢,區(qū)別就是前者是由客戶端去做請(qǐng)求,后者是由系統(tǒng)配置的 DNS 服務(wù)器做請(qǐng)求,得到結(jié)果后將數(shù)據(jù)返回給客戶端。

PS:DNS 是基于 UDP 做的查詢,大家也可以考慮下為什么之前不考慮使用 TCP 去實(shí)現(xiàn)。

接下來是 TCP 握手,應(yīng)用層會(huì)下發(fā)數(shù)據(jù)給傳輸層,這里 TCP 協(xié)議會(huì)指明兩端的端口號(hào),然后下發(fā)給網(wǎng)絡(luò)層。網(wǎng)絡(luò)層中的 IP 協(xié)議會(huì)確定 IP 地址,并且指示了數(shù)據(jù)傳輸中如何跳轉(zhuǎn)路由器。然后包會(huì)再被封裝到數(shù)據(jù)鏈路層的數(shù)據(jù)幀結(jié)構(gòu)中,最后就是物理層面的傳輸了。

在這一部分中,可以詳細(xì)說下 TCP 的握手情況以及 TCP 的一些特性。
第一次握手

客戶端向服務(wù)端發(fā)送連接請(qǐng)求報(bào)文段。該報(bào)文段中包含自身的數(shù)據(jù)通訊初始序號(hào)。請(qǐng)求發(fā)送后,客戶端便進(jìn)入 SYN-SENT 狀態(tài)。

第二次握手

服務(wù)端收到連接請(qǐng)求報(bào)文段后,如果同意連接,則會(huì)發(fā)送一個(gè)應(yīng)答,該應(yīng)答中也會(huì)包含自身的數(shù)據(jù)通訊初始序號(hào),發(fā)送完成后便進(jìn)入 SYN-RECEIVED 狀態(tài)。

第三次握手

當(dāng)客戶端收到連接同意的應(yīng)答后,還要向服務(wù)端發(fā)送一個(gè)確認(rèn)報(bào)文??蛻舳税l(fā)完這個(gè)報(bào)文段后便進(jìn)入 ESTABLISHED 狀態(tài),服務(wù)端收到這個(gè)應(yīng)答后也進(jìn)入 ESTABLISHED 狀態(tài),此時(shí)連接建立成功。

PS:第三次握手中可以包含數(shù)據(jù),通過快速打開(TFO)技術(shù)就可以實(shí)現(xiàn)這一功能。其實(shí)只要涉及到握手的協(xié)議,都可以使用類似 TFO 的方式,客戶端和服務(wù)端存儲(chǔ)相同的 cookie,下次握手時(shí)發(fā)出 cookie 達(dá)到減少 RTT 的目的。

為什么 TCP 建立連接需要三次握手,明明兩次就可以建立起連接?
因?yàn)檫@是為了防止出現(xiàn)失效的連接請(qǐng)求報(bào)文段被服務(wù)端接收的情況,從而產(chǎn)生錯(cuò)誤。

可以想象如下場景??蛻舳税l(fā)送了一個(gè)連接請(qǐng)求 A,但是因?yàn)榫W(wǎng)絡(luò)原因造成了超時(shí),這時(shí) TCP 會(huì)啟動(dòng)超時(shí)重傳的機(jī)制再次發(fā)送一個(gè)連接請(qǐng)求 B。此時(shí)請(qǐng)求順利到達(dá)服務(wù)端,服務(wù)端應(yīng)答完就建立了請(qǐng)求,然后接收數(shù)據(jù)后釋放了連接。

假設(shè)這時(shí)候連接請(qǐng)求 A 在兩端關(guān)閉后終于抵達(dá)了服務(wù)端,那么此時(shí)服務(wù)端會(huì)認(rèn)為客戶端又需要建立 TCP 連接,從而應(yīng)答了該請(qǐng)求并進(jìn)入 ESTABLISHED 狀態(tài)。但是客戶端其實(shí)是 CLOSED 的狀態(tài),那么就會(huì)導(dǎo)致服務(wù)端一直等待,造成資源的浪費(fèi)。

PS:在建立連接中,任意一端掉線,TCP 都會(huì)重發(fā) SYN 包,一般會(huì)重試五次,在建立連接中可能會(huì)遇到 SYN Flood 攻擊。遇到這種情況你可以選擇調(diào)低重試次數(shù)或者干脆在不能處理的情況下拒絕請(qǐng)求。

當(dāng) TCP 握手結(jié)束后就會(huì)進(jìn)行 TLS 握手,然后就開始正式的傳輸數(shù)據(jù)。

在這一部分中,可以詳細(xì)說下 TLS 的握手情況以及兩種加密方式的內(nèi)容。

數(shù)據(jù)在進(jìn)入服務(wù)端之前,可能還會(huì)先經(jīng)過負(fù)責(zé)負(fù)載均衡的服務(wù)器,它的作用就是將請(qǐng)求合理的分發(fā)到多臺(tái)服務(wù)器上,這時(shí)假設(shè)服務(wù)端會(huì)響應(yīng)一個(gè) HTML 文件。

首先瀏覽器會(huì)判斷狀態(tài)碼是什么,如果是 200 那就繼續(xù)解析,如果 400 或 500 的話就會(huì)報(bào)錯(cuò),如果 300 的話會(huì)進(jìn)行重定向,這里會(huì)有個(gè)重定向計(jì)數(shù)器,避免過多次的重定向,超過次數(shù)也會(huì)報(bào)錯(cuò)。

瀏覽器開始解析文件,如果是 gzip 格式的話會(huì)先解壓一下,然后通過文件的編碼格式知道該如何去解碼文件。

文件解碼成功后會(huì)正式開始渲染流程,先會(huì)根據(jù) HTML 構(gòu)建 DOM 樹,有 CSS 的話會(huì)去構(gòu)建 CSSOM 樹。如果遇到 script 標(biāo)簽的話,會(huì)判斷是否存在 async 或者 defer ,前者會(huì)并行進(jìn)行下載并執(zhí)行 JS,后者會(huì)先下載文件,然后等待 HTML 解析完成后順序執(zhí)行。

如果以上都沒有,就會(huì)阻塞住渲染流程直到 JS 執(zhí)行完畢。遇到文件下載的會(huì)去下載文件,這里如果使用 HTTP/2 協(xié)議的話會(huì)極大的提高多圖的下載效率。
CSSOM 樹和 DOM 樹構(gòu)建完成后會(huì)開始生成 Render 樹,這一步就是確定頁面元素的布局、樣式等等諸多方面的東西

在生成 Render 樹的過程中,瀏覽器就開始調(diào)用 GPU 繪制,合成圖層,將內(nèi)容顯示在屏幕上了。

這一部分就是渲染原理中講解到的內(nèi)容,可以詳細(xì)的說明下這一過程。并且在下載文件時(shí),也可以說下通過 HTTP/2 協(xié)議可以解決隊(duì)頭阻塞的問題。
我們知道執(zhí)行 JS 有一個(gè) JS 引擎,那么執(zhí)行渲染也有一個(gè)渲染引擎。同樣,渲染引擎在不同的瀏覽器中也不是都相同的。比如在 Firefox 中叫做 Gecko,在 Chrome 和 Safari 中都是基于 WebKit 開發(fā)的。在這一章節(jié)中,我們也會(huì)主要學(xué)習(xí)關(guān)于 WebKit 的這部分渲染引擎內(nèi)容。

瀏覽器接收到 HTML 文件并轉(zhuǎn)換為 DOM 樹

當(dāng)我們打開一個(gè)網(wǎng)頁時(shí),瀏覽器都會(huì)去請(qǐng)求對(duì)應(yīng)的 HTML 文件。雖然平時(shí)我們寫代碼時(shí)都會(huì)分為 JS、CSS、HTML 文件,也就是字符串,但是計(jì)算機(jī)硬件是不理解這些字符串的,所以在網(wǎng)絡(luò)中傳輸?shù)膬?nèi)容其實(shí)都是 01 這些字節(jié)數(shù)據(jù)。當(dāng)瀏覽器接收到這些字節(jié)數(shù)據(jù)以后,它會(huì)將這些字節(jié)數(shù)據(jù)轉(zhuǎn)換為字符串,也就是我們寫的代碼。

當(dāng)數(shù)據(jù)轉(zhuǎn)換為字符串以后,瀏覽器會(huì)先將這些字符串通過詞法分析轉(zhuǎn)換為標(biāo)記(token),這一過程在詞法分析中叫做標(biāo)記化(tokenization)。

那么什么是標(biāo)記呢?這其實(shí)屬于編譯原理這一塊的內(nèi)容了。簡單來說,標(biāo)記還是字符串,是構(gòu)成代碼的最小單位。這一過程會(huì)將代碼分拆成一塊塊,并給這些內(nèi)容打上標(biāo)記,便于理解這些最小單位的代碼是什么意思。

當(dāng)結(jié)束標(biāo)記化后,這些標(biāo)記會(huì)緊接著轉(zhuǎn)換為 Node,最后這些 Node 會(huì)根據(jù)不同 Node 之前的聯(lián)系構(gòu)建為一顆 DOM 樹。

以上就是瀏覽器從網(wǎng)絡(luò)中接收到 HTML 文件然后一系列的轉(zhuǎn)換過程。

當(dāng)然,在解析 HTML 文件的時(shí)候,瀏覽器還會(huì)遇到 CSS 和 JS 文件,這時(shí)候?yàn)g覽器也會(huì)去下載并解析這些文件,接下來就讓我們先來學(xué)習(xí)瀏覽器如何解析 CSS 文件。

將 CSS 文件轉(zhuǎn)換為 CSSOM 樹

其實(shí)轉(zhuǎn)換 CSS 到 CSSOM 樹的過程和上一小節(jié)的過程是極其類似的

在這一過程中,瀏覽器會(huì)確定下每一個(gè)節(jié)點(diǎn)的樣式到底是什么,并且這一過程其實(shí)是很消耗資源的。因?yàn)闃邮侥憧梢宰孕性O(shè)置給某個(gè)節(jié)點(diǎn),也可以通過繼承獲得。在這一過程中,瀏覽器得遞歸 CSSOM 樹,然后確定具體的元素到底是什么樣式。

如果你有點(diǎn)不理解為什么會(huì)消耗資源的話,我這里舉個(gè)例子

<div>
  <a> <span></span> </a>
</div>
<style>
  span {
    color: red;
  }
  div > a > span {
    color: red;
  }
</style>

對(duì)于第一種設(shè)置樣式的方式來說,瀏覽器只需要找到頁面中所有的 span 標(biāo)簽然后設(shè)置顏色,但是對(duì)于第二種設(shè)置樣式的方式來說,瀏覽器首先需要找到所有的 span 標(biāo)簽,然后找到 span 標(biāo)簽上的 a 標(biāo)簽,最后再去找到 div 標(biāo)簽,然后給符合這種條件的 span 標(biāo)簽設(shè)置顏色,這樣的遞歸過程就很復(fù)雜。所以我們應(yīng)該盡可能的避免寫過于具體的 CSS 選擇器,然后對(duì)于 HTML 來說也盡量少的添加無意義標(biāo)簽,保證層級(jí)扁平

生成渲染樹

當(dāng)我們生成 DOM 樹和 CSSOM 樹以后,就需要將這兩棵樹組合為渲染樹。

在這一過程中,不是簡單的將兩者合并就行了。渲染樹只會(huì)包括需要顯示的節(jié)點(diǎn)和這些節(jié)點(diǎn)的樣式信息,如果某個(gè)節(jié)點(diǎn)是 display: none 的,那么就不會(huì)在渲染樹中顯示。

當(dāng)瀏覽器生成渲染樹以后,就會(huì)根據(jù)渲染樹來進(jìn)行布局(也可以叫做回流),然后調(diào)用 GPU 繪制,合成圖層,顯示在屏幕上。對(duì)于這一部分的內(nèi)容因?yàn)檫^于底層,還涉及到了硬件相關(guān)的知識(shí),這里就不再繼續(xù)展開內(nèi)容了。

那么通過以上內(nèi)容,我們已經(jīng)詳細(xì)了解到了瀏覽器從接收文件到將內(nèi)容渲染在屏幕上的這一過程。接下來,我們將會(huì)來學(xué)習(xí)上半部分遺留下來的一些知識(shí)點(diǎn)。

為什么操作 DOM 慢

想必大家都聽過操作 DOM 性能很差,但是這其中的原因是什么呢?

因?yàn)?DOM 是屬于渲染引擎中的東西,而 JS 又是 JS 引擎中的東西。當(dāng)我們通過 JS 操作 DOM 的時(shí)候,其實(shí)這個(gè)操作涉及到了兩個(gè)線程之間的通信,那么勢(shì)必會(huì)帶來一些性能上的損耗。操作 DOM 次數(shù)一多,也就等同于一直在進(jìn)行線程之間的通信,并且操作 DOM 可能還會(huì)帶來重繪回流的情況,所以也就導(dǎo)致了性能上的問題。

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

友情鏈接更多精彩內(nèi)容