說一說從 URL 輸入到頁面呈現(xiàn)到底發(fā)生了什么?

前言

這是面試過程中一道高頻考題。

從面試官的角度思考:

  • 出現(xiàn)頻繁,可能是因?yàn)槊嬖嚬偻ǔO矚g問一些考察可深可淺的題目
  • 很多面試管喜歡根據(jù)我們應(yīng)聘者的考題回答中,甚至我們隨口說到的知識(shí)點(diǎn),繼續(xù)追問

基本回答:

  • 瀏覽器解析 URL 獲取協(xié)議,主機(jī),端口, path
  • 瀏覽器獲取主機(jī) ip 地址
  • 建立 TCP 連接,然后發(fā)送 HTTP 請(qǐng)求
  • 服務(wù)器將響應(yīng)報(bào)文通過 TCP 連接發(fā)送回瀏覽器,瀏覽器接收 HTTP 響應(yīng),根據(jù)資源類型決定如何處理(假設(shè)資源為 HTML 文檔)
  • 解析 HTML 文檔,構(gòu)件 DOM 樹,下載資源,構(gòu)造 CSSOM 樹,執(zhí)行 js 腳本,最后展現(xiàn)出來給用戶

如果應(yīng)聘者只回答了上述步驟,很多關(guān)鍵步驟(前端應(yīng)該了解的知識(shí)點(diǎn))沒有提及,很有可能達(dá)不到面試官想要的回答效果。

筆者針對(duì)一些關(guān)鍵步驟,具體展開說明。讓這道題成為我們面試考卷中的加分項(xiàng)

網(wǎng)絡(luò)請(qǐng)求

構(gòu)建請(qǐng)求

瀏覽器會(huì)構(gòu)建請(qǐng)求行:

// 請(qǐng)求方法是 GET,路徑為根路徑,HTTP 協(xié)議版本為 1.1
GET / HTTP/1.1

然后根據(jù) Cache-control 和 Expires 字段,檢查強(qiáng)緩存,如果命中直接使用,否則進(jìn)入下一步。關(guān)于強(qiáng)緩存,如果不清楚可以參考下圖:

DNS 解析

由于我們輸入的是域名,而數(shù)據(jù)包是通過IP地址傳給對(duì)方的。因此我們需要得到域名對(duì)應(yīng)的IP地址。這個(gè)過程需要依賴一個(gè)服務(wù)系統(tǒng),這個(gè)系統(tǒng)將域名和 IP 一一映射,我們將這個(gè)系統(tǒng)就叫做DNS(域名系統(tǒng))。

DNS 協(xié)議提供通過域名查找 IP 地址,或逆向從 IP 地址反查域名的服務(wù)。得到具體 IP 的過程就是DNS解析。

DNS 是一個(gè)網(wǎng)絡(luò)服務(wù)器,我們的域名解析簡(jiǎn)單來說就是在 DNS 上記錄一條信息記錄。

例如 baidu.com 220.114.23.56(服務(wù)器外網(wǎng)IP地址)80(服務(wù)器端口號(hào))

瀏覽器通過域名去查詢 URL 對(duì)應(yīng)的 IP :

  • 瀏覽器緩存:瀏覽器會(huì)按照一定的頻率緩存 DNS 記錄
  • 操作系統(tǒng)緩存:如果瀏覽器緩存中找不到需要的 DNS 記錄,那就去操作系統(tǒng)中找
  • 路由緩存:路由器也有 DNS 緩存
  • ISP 的 DNS 服務(wù)器:ISP 是互聯(lián)網(wǎng)服務(wù)提供商( Internet Service Provider )的簡(jiǎn)稱,ISP 有專門的 DNS 服務(wù)器應(yīng)對(duì) DNS 查詢請(qǐng)求
  • 根服務(wù)器:ISP 的 DNS 服務(wù)器還找不到的話,它就會(huì)向根服務(wù)器發(fā)出請(qǐng)求,進(jìn)行遞歸查詢(DNS 服務(wù)器先問根域名服務(wù)器 .com 域名服務(wù)器的 IP 地址,然后再問 .baidu 域名服務(wù)器,依次類推)

建立 TCP 連接

TCP 三次握手的過程如下:

  • 客戶端發(fā)送一個(gè)帶 SYN=1,Seq=X 的數(shù)據(jù)包到服務(wù)器端口(第一次握手,由瀏覽器發(fā)起,告訴服務(wù)器我要發(fā)送請(qǐng)求了)
  • 服務(wù)器發(fā)回一個(gè)帶 SYN=1, ACK=X+1, Seq=Y 的響應(yīng)包以示傳達(dá)確認(rèn)信息(第二次握手,由服務(wù)器發(fā)起,告訴瀏覽器我準(zhǔn)備接受了,你趕緊發(fā)送吧)
  • 客戶端再回傳一個(gè)帶 ACK=Y+1, Seq=Z 的數(shù)據(jù)包,代表“握手結(jié)束”(第三次握手,由瀏覽器發(fā)送,告訴服務(wù)器,我馬上就發(fā)了,準(zhǔn)備接受吧)

謝希仁著《計(jì)算機(jī)網(wǎng)絡(luò)》中講“三次握手”的目的是“為了防止已失效的連接請(qǐng)求報(bào)文段突然又傳送到了服務(wù)端,因而產(chǎn)生錯(cuò)誤”。

發(fā)送 HTTP 請(qǐng)求

現(xiàn)在 TCP 連接建立完畢,瀏覽器可以和服務(wù)器開始通信,即開始發(fā)送 HTTP 請(qǐng)求。瀏覽器發(fā) HTTP 請(qǐng)求要攜帶三樣?xùn)|西:請(qǐng)求行、請(qǐng)求頭請(qǐng)求體

[圖片上傳失敗...(image-f9c53-1587981019451)]

1.請(qǐng)求行包含請(qǐng)求方法、URL、協(xié)議版本

  • 請(qǐng)求方法包含 8 種:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE
  • URL 即請(qǐng)求地址,由 <協(xié)議>://<主機(jī)>:<端口>/<路徑>?<參數(shù)> 組成
  • 協(xié)議版本即 http 版本號(hào)
POST /user.html HTTP/1.1

2.請(qǐng)求頭包含請(qǐng)求的附加信息,由關(guān)鍵字/值對(duì)組成,如下

// 服務(wù)器可以接受的文件格式
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng;q=0.8,application/signed-exchange;v=b3
// 指定瀏覽器可以支持的 Web 服務(wù)器返回的內(nèi)容壓縮編碼類型
Accept-Encoding: gzip, deflate, br
// 瀏覽器支持的語言
Accept-Language: zh-CN,zh;q=0.9
// 緩存機(jī)制
Cache-Control: no-cache
// 是否需要持久連接
Connection: keep-alive
// 發(fā)送該請(qǐng)求域名下所有 Cookie 值到服務(wù)器
Cookie: /* 省略cookie信息 */
// 指定請(qǐng)求的服務(wù)器的域名和端口號(hào)
Host: www.baidu.com
Pragma: no-cache
Upgrade-Insecure-Requests: 1
// 用戶代理 UA,包含發(fā)出請(qǐng)求的用戶信息
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1

3.請(qǐng)求體,可以承載多個(gè)請(qǐng)求參數(shù)的數(shù)據(jù),包含回車符、換行符和請(qǐng)求數(shù)據(jù),一般在 POST 方法下存在。

網(wǎng)絡(luò)響應(yīng)

跟請(qǐng)求部分類似,網(wǎng)絡(luò)響應(yīng)具有三個(gè)部分:響應(yīng)行響應(yīng)頭響應(yīng)體。

1.響應(yīng)行包含:協(xié)議版本,狀態(tài)碼,狀態(tài)碼描述

HTTP/1.1 200 OK

狀態(tài)碼規(guī)則如下:

  • 1xx:指示信息--表示請(qǐng)求已接收,繼續(xù)處理
  • 2xx:成功--表示請(qǐng)求已被成功接收、理解、接受
  • 3xx:重定向--要完成請(qǐng)求必須進(jìn)行更進(jìn)一步的操作
  • 4xx:客戶端錯(cuò)誤--請(qǐng)求有語法錯(cuò)誤或請(qǐng)求無法實(shí)現(xiàn)
  • 5xx:服務(wù)器端錯(cuò)誤--服務(wù)器未能實(shí)現(xiàn)合法的請(qǐng)求

2.響應(yīng)頭部包含響應(yīng)報(bào)文的附加信息,由 名/值 對(duì)組成,如下:

// 緩存機(jī)制
Cache-Control: no-cache
Connection: keep-alive
Content-Encoding: gzip
// 表示具體請(qǐng)求中的媒體類型信息,決定瀏覽器將以什么形式、什么編碼讀取這個(gè)文件
Content-Type: text/html;charset=utf-8
// 原始服務(wù)器消息發(fā)出的時(shí)間
Date: Wed, 04 Dec 2019 12:29:13 GMT
// Web 服務(wù)器軟件名稱
Server: apache
// 由服務(wù)器端向客戶端發(fā)送 cookie
Set-Cookie: rsv_i=f9a0SIItKqzv7kqgAAgphbGyRts3RwTg%2FLyU3Y5Eh5LwyfOOrAsvdezbay0QqkDqFZ0DfQXby4wXKT8Au8O7ZT9UuMsBq2k; path=/; domain=.baidu.com

這里注意下 Set-Cookie 中關(guān)于網(wǎng)絡(luò)安全方面的兩個(gè)值:HttpOnly、SameSite

設(shè)置了 HttpOnly 屬性的 cookie 不能使用 JavaScript 經(jīng)由 Document.cookie 屬性、XMLHttpRequest 和 Request APIs 進(jìn)行訪問,以防范跨站腳本攻擊(XSS)。

SameSite=Lax 允許服務(wù)器設(shè)定一則 cookie 不隨著跨域請(qǐng)求一起發(fā)送,這樣可以在一定程度上防范跨站請(qǐng)求偽造攻擊(CSRF)。

響應(yīng)完成之后要判斷 Connection 字段,如果請(qǐng)求頭或響應(yīng)頭中包含 Connection: Keep-Alive ,表示建立了持久連接,這樣 TCP 連接會(huì)一直保持,之后請(qǐng)求統(tǒng)一站點(diǎn)的資源會(huì)復(fù)用這個(gè)連接。
否則斷開 TCP 連接, 請(qǐng)求-響應(yīng)流程結(jié)束。

3.響應(yīng)主體包含回車符、換行符和響應(yīng)返回?cái)?shù)據(jù),并不是所有響應(yīng)報(bào)文都有響應(yīng)數(shù)據(jù)

總結(jié)瀏覽器端的網(wǎng)絡(luò)請(qǐng)求過程:

image

瀏覽器解析渲染頁面

image

瀏覽器解析渲染頁面分為以下五個(gè)步驟:

  • 根據(jù) HTML 解析出 DOM 樹
  • 根據(jù) CSS 解析生成 CSS 規(guī)則樹
  • 結(jié)合 DOM 樹和 CSS 規(guī)則樹,生成渲染樹
  • 根據(jù)渲染樹計(jì)算每一個(gè)節(jié)點(diǎn)的信息
  • 根據(jù)計(jì)算好的信息繪制頁面

回流時(shí),以上流程會(huì)重新走一遍。重繪時(shí),會(huì)重新計(jì)算樣式,跳過中間步驟直接生成繪制列表??梢姡乩L不一定導(dǎo)致回流,但回流一定發(fā)生了重繪。

image

構(gòu)建 DOM 樹

  • HTML語法定義

    HTML的詞匯與句法定義在w3c組織創(chuàng)建的規(guī)范中。當(dāng)前版本是HTML4,HTML5的工作正在進(jìn)行中。

  • 不是上下文無關(guān)語法

    在對(duì)解析器的介紹中看到,語法可以用類似 BNF 的格式規(guī)范地定義。不幸的是所有常規(guī)解析器的討論都不適用于 HTML (我提及它們并不是為了娛樂,它們可以用于解析 CSS 和 JavaScript )。HTML 無法用解析器所需的上下文無關(guān)的語法來定義。過去 HTML 格式規(guī)范由 DTD (Document Type Definition) 來定義,但它不是一個(gè)上下文無關(guān)語法。

    HTML 與 XML 相當(dāng)接近。XML 有許多可用的解析器。HTML 還有一個(gè) XML 變種叫 XHTML ,那么它們主要區(qū)別在哪里呢?區(qū)別在于 HTML 應(yīng)用更加”寬容”,它容許你漏掉一些開始或結(jié)束標(biāo)簽等。它整個(gè)是一個(gè)“軟”句法,不像 XML 那樣嚴(yán)格死板。 總的來說這一看似細(xì)微的差別造成了兩個(gè)不同的世界。一方面這使得 HTML 很流行,因?yàn)樗菽愕腻e(cuò)誤,使網(wǎng)頁作者的生活變得輕松。另一方面,它使編寫語法格式變得困難。所以綜合來說,HTML 解析并不簡(jiǎn)單,現(xiàn)成的上下文相關(guān)解析器搞不定,XML 解析器也不行。

  • 解析算法

    • 標(biāo)記化
    • 建樹

    對(duì)應(yīng)的兩個(gè)過程就是分詞和語法分析(參考Babel 編譯的解析過程)。

    這里舉例重點(diǎn)介紹下 HTML5容錯(cuò)機(jī)制

    • 使用 </br> 而不是 <br>

      if (t->isCloseTag(brTag) && m_document->inCompatMode()) {
          reportError(MalformedBRError);
          t->beginTag = true;
      }
      

      全部換為 <br> 的形式。

    • 表格離散

      <table>
          <table>
              <tr><td>inner table</td></tr>
          </table>
          <tr><td>outer table</td></tr>
      </table>
      

      WebKit 會(huì)自動(dòng)轉(zhuǎn)換為:

      <table>
          <tr><td>outer table</td></tr>
      </table>
      <table>
          <tr><td>inner table</td></tr>
      </table>
      
    • 表單元素嵌套

      這時(shí)候直接忽略里面的 form 。

樣式計(jì)算

CSS 樣式來源一般為三種:

  • link 標(biāo)簽引用
  • style 標(biāo)簽中樣式
  • 元素內(nèi)嵌 style 屬性
格式化樣式表

瀏覽器無法直接識(shí)別 CSS 樣式文本,這里渲染引擎接收到 CSS 文本之后將其轉(zhuǎn)化為一個(gè)結(jié)構(gòu)話的對(duì)象,即 styleSheets 。

可以在瀏覽器控制臺(tái)輸入 document.styleSheets 來查看這個(gè)最終結(jié)構(gòu)(包含上述三種 CSS 來源)。

標(biāo)準(zhǔn)化樣式屬性

有一些渲染引擎不容易直接理解的 CSS 樣式數(shù)值,需要在計(jì)算樣式之前將它們標(biāo)準(zhǔn)化。如:em -> px,red -> #ff0000bold -> 700 等等。

計(jì)算每個(gè)節(jié)點(diǎn)的具體樣式

計(jì)算具體樣式主要遵循兩個(gè)規(guī)則:繼承層疊

  • 繼承:

    每個(gè)子節(jié)點(diǎn)都會(huì)默認(rèn)繼承父節(jié)點(diǎn)的樣式屬性,如果父節(jié)點(diǎn)中沒有找到,就采用瀏覽器默認(rèn)樣式,也叫 UserAgent樣式

  • 層疊:

    CSS 的層疊性體現(xiàn)在,最終的樣式取決與各個(gè)屬性共同作用的結(jié)果。

計(jì)算完樣式之后,所有樣式值會(huì)被掛載到 window.getComputedStyle 中,也就是可以通過 JS 獲取計(jì)算后的樣式。

生成布局樹

布局樹生成主要分兩部:

  • 遍歷生成的 DOM 樹節(jié)點(diǎn),并把它們添加到布局樹中
  • 計(jì)算布局樹節(jié)點(diǎn)的坐標(biāo)位置

布局樹只包含可見元素,對(duì)于 head 標(biāo)簽和設(shè)置了 display: none 的元素將不會(huì)被放入其中。

如果想了解布局的細(xì)節(jié),可以讀一讀人人 FED 團(tuán)隊(duì)的文章從Chrome源碼看瀏覽器如何layout布局。

構(gòu)建圖層樹

這里分兩種情況,一種是顯式合成,一種是隱式合成

顯式合成

一、擁有層疊上下文的節(jié)點(diǎn)

層疊上下文也基本上是有一些特定的 CSS 屬性創(chuàng)建的,一般有以下情況:

  1. HTML 根元素本身就具有層疊上下文

  2. 普通元素設(shè)置 position 不為 static 并且設(shè)置了 z-index 屬性,會(huì)產(chǎn)生層疊上下文

  3. 元素的 opacity 值不是 1

  4. 元素的 transform 值不是 none

  5. 元素的 filter 值不是 none

  6. 元素的 isolation 值是 isolate

  7. will-change 指定的屬性值為上面任意一個(gè)

二、需要剪裁的地方

比如一個(gè) div,你只給他設(shè)置 100 * 100 像素的大小,而你在里面放了非常多的文字,那么超出的文字部分就需要被剪裁。當(dāng)然如果出現(xiàn)了滾動(dòng)條,那么滾動(dòng)條會(huì)被單獨(dú)提升為一個(gè)圖層。

隱式合成

簡(jiǎn)單說就是層疊等級(jí)低的節(jié)點(diǎn)被提升為單獨(dú)的圖層之后,那么所有層疊等級(jí)比它高的節(jié)點(diǎn)都會(huì)成為一個(gè)單獨(dú)的圖層。

這個(gè)隱式合成其實(shí)隱藏著巨大風(fēng)險(xiǎn),如果在一個(gè)大型應(yīng)用中,當(dāng)一個(gè) z-index 比較低的元素被提升為單獨(dú)圖層之后,層疊在它上面的元素統(tǒng)統(tǒng)會(huì)被提升為單獨(dú)的圖層,可能會(huì)增加上千個(gè)圖層,大大增加內(nèi)存壓力,甚至直接讓頁面崩潰。這就是層爆炸的原理

當(dāng)需要 repaint 時(shí),只需要 repaint 本身,而不會(huì)影響到其他層。

生成繪制列表

渲染引擎會(huì)將圖層的繪制拆分成一個(gè)個(gè)繪制指令,比如先畫背景、再描繪邊框......然后將這些指令按順序組合成一個(gè)待繪制列表。

大家可以 F12 打開 Chrome 開發(fā)者工具,在設(shè)置欄展開 more tools ,然后選擇 Layers 面板,就能看到繪制列表了。

后面就是渲染進(jìn)程的主線程把繪制列表提交給合成線程。然后合成線程選擇視口附近的圖塊,把它交給柵格化線程池生成位圖。

柵格化操作完成后,合成線程會(huì)生成一個(gè)繪制指令 DrawQuad,并發(fā)送給瀏覽器進(jìn)程。瀏覽器進(jìn)程中的 viz 組件 接收到命令,把頁面內(nèi)容繪制到內(nèi)存,也就是生成了頁面。

斷開連接

當(dāng)數(shù)據(jù)傳送完畢,需要斷開 TCP 連接,此時(shí)發(fā)起四次揮手。

image
  • 發(fā)起方往被動(dòng)方發(fā)送報(bào)文,F(xiàn)in、Ack、Seq,表示已經(jīng)沒有數(shù)據(jù)傳輸了。并進(jìn)入 FIN_WAIT_1 狀態(tài)。(請(qǐng)求報(bào)文發(fā)送完成)
  • 被動(dòng)方發(fā)送報(bào)文,Ack、Seq,表示同意關(guān)閉請(qǐng)求。此時(shí)主機(jī)發(fā)起方進(jìn)入 FIN_WAIT_2 狀態(tài)。(請(qǐng)求報(bào)文接受完成)
  • 被動(dòng)方向發(fā)起方發(fā)送報(bào)文段,F(xiàn)in、Ack、Seq,請(qǐng)求關(guān)閉連接。并進(jìn)入 LAST_ACK 狀態(tài)。(響應(yīng)報(bào)文發(fā)送完成)
  • 發(fā)起方向被動(dòng)方發(fā)送報(bào)文段,Ack、Seq。然后進(jìn)入等待 TIME_WAIT 狀態(tài)。被動(dòng)方收到發(fā)起方的報(bào)文段以后關(guān)閉連接。發(fā)起方等待一定時(shí)間未收到回復(fù),則正常關(guān)閉。(響應(yīng)報(bào)文接受完成)

參考文章

感謝

如果本文對(duì)你有幫助,就點(diǎn)個(gè)贊支持下吧!感謝閱讀。

?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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