URL解析全過程


  如果我們需要知道一次詳細的url解析過程,需要了解一些基礎性的知識和概念,如什么是RUL,什么是DNS?下面分別來一一進行介紹。

  URL(Uniform Resource Locator): 統一資源定位符,URL是使用瀏覽器訪問web頁面時需要輸入的網頁地址。如:https://www.baidu.com/就是URL。也被稱為“網址”。

  我們首先看一下https://www.bilibili.com/?spm_id_from=333.851.b_7265706f7274466972737431.1這個網址的組成部分

    1.傳輸協議:它主要是用於傳輸客戶端和服務器端通信的信息。

      http協議(超文本傳輸協議):它是一個基於請求/響應模式的無狀態協議。支持除文本外的富媒體資源,如圖片,視頻等;

         https它是http+ssl(加密傳輸):https就是在http下加了SSL層從而來保護交換數據的隱私和完整。一般來說它可以通過證書等相關信息確認網站的真實性,建立加密的信息通道,保證數據內容的完整。一般用於支付類網站如:https://www.alipay.com/;

      ftp協議(文件上傳下載協議):一般用於客戶端和服務器端文件的直接傳輸。

    2. 服務器域名:用戶訪問網頁時,DNS服務器會根據用戶提供的域名查到相應的IP地址。上面使用的是:bilibili.com

    3. 端口號:端口是服務器用於內外部通信的通道,一般是0~65535之間。它可以用來區分同一台服務器上不同項目,當用戶訪問服務器時必須從要求的端口訪問才能正常打開網頁。上面用的是端口號是80。自己寫地址時,不加端口號,瀏覽器會按照默認端口號自動補充上。

    4. 問號傳參:客戶端想把信息傳遞給服務器,可以基於問號傳參進行處理。還可以用於客戶端一個頁面跳轉到另一個頁面。在新的頁面中獲取到舊頁面的某些內容,也是可以基於問號傳參來實現。也可以用於組件傳參。

  與URL相比,還有URI(Uniform Resource Identifier) 統一資源標識符。URI就是由某個協議方案表示的資源的定位標識符。協議 方案是指訪問資源所使用的協議類型名稱。

  一個完整的URL解析要分為以下幾步:1.url解析 2.緩存檢查 3. DNS解析 4. 建立TCP鏈接 5. 發送HTTP請求 服務器處理 6. 關閉TCP連接通道 7. 客戶端渲染。

  一、URL解析 

  在一個完整的URL解析中我們需要了解什么是http,怎么進行編碼解碼以有什么是HSTS。

  1、HTTP協議

  什么是HTTP協議?HTTP協議是Hyper Text Transfer Protocol(超文本傳輸協議)的縮寫,它基於TCP/IP通信協議來傳遞數據,瀏覽器作為HTTP客戶端通過URL向HTTP服務端即WEB服務器發送所有請求。Web服務器根據接收到的請求后,向客戶端發送響應信息。

  如果說HTTP是因特網的信使,那么HTTP報文就是它傳遞的信息,所有客戶端和服務器端傳輸的信息統稱為“報文”。如果我們每完成一次請求和響應那就可以稱之為一次HTTP事務了。

  一個HTTP請求報文由請求行(request line)、請求頭部(header)、空行和請求數據4個部分組成。

  請求行它是請求報文的起始行,包含了一個方法和一個請求URL,這個方法描述了服務器應該執行的操作,請求URL描述了要對哪個資源執行這個方法。請求行中還包含HTTP的版本,用來告知服務器,客戶端使用的是哪種HTTP。請求行分為三個部分:請求方法、請求地址URL和HTTP協議版本,它們之間用空格分割。

   請求方法:請求的起始行以方法作為開始,方法用來告訴服務器要做什么,常見的請求方法有以下幾種:GET系列:GET / DELETE / HEAD / OPTIONS,POST系列:POST / PUT 下面分別進行介紹:

  GET系列 : 一般認為是從服務器獲取到信息,當然也可以把客戶端的信息傳遞給服務器,給的少,拿的多。

    GET: GET是最常用的方法。通常用於請求服務器發送某個資源。GET方法要求服務器將URL定位的資源放在響應報文的數據部分,發送給客戶端。使用GET方法時,請求參數和對應的值附加在URL后面,利用問號‘?’代表URL的結尾與請求參數的開始,傳遞參數長度受限制。

    DELETE: DELETE方法所做的事情就是請服務器刪除請求URL所指定的資源。一般應用於想刪除服務器上的文件或者是一些大量的信息。

    HEAD: 只需要獲取響應頭的信息就可,響應主體信息不接受。這就允許客戶端在未獲取實際資源的情況下,對資源的首部進行檢査。

    OPTIONS: 試探性請求,這個請求用於校驗客戶端和服務器端是否正常連接。

  POST系列:一般認為是給服務器推送信息,給的多,拿的少。

    POST: POST方法將請求參數封裝在HTTP請求數據中,以名稱/值的形式出現,可以傳輸大量數據,這樣POST方式對傳送的數據大小沒有限制,而且也不會顯示在URL中.POST方式大多用於頁面的表單中.

    PUT: PUT方法會向服務器寫入文檔。一般用於給服務器傳遞文件或者是大的數控,如文本編輯器編輯的內容。

  GET與POST的區別:

    GET傳遞的服務器的信息一般都是基於url地址問號傳參來進行實現,如:./data.json?x=1&name=davina&xxx=xxx......。地址中‘?’之后的部分就是通過GET發送的請求數據,各個數據之間用‘&’符號隔開。

    不足:GET方式不適合傳送私密數據。不同瀏覽器對地址字符的限制也不盡相同。一般最多只能識別1024個字符,所以如果需要傳送大量數據的時候,也不適合使用GET方式。

    POST: 允許客戶端給服務器提供信息較多,POST傳遞給服務器的信息一般基於請求主體來實現,客戶端還可以基於設置請求頭,把一些簡要的信息傳遞給服務器。

    區別:

    GET傳遞給服務器的信息小於POST.因為不同瀏覽器對地址字符的限制也不盡相同。一般最多只能識別1024個字符,超出瀏覽器限制的部分,內容會被自動裁切掉,POST請求理論上是沒有長度限制(請求主體沒有設置大小限制),但在真實的項目中為了保證數據傳輸高效,我們一般都會手動做限制。

    安全問題:POST相對GET安全一些,項目中涉及安全信息的傳輸都是要用POST。主要原因是:get基於url傳數據,但容易被url劫持掉,這樣不安全,post相對來說安全,但也不是絕對安全。所以對於重要信息傳輸也需要進行手動加密處理。

    緩存問題:瀏覽器會在處理GET請求時,如果兩次請求的地址后面參數一致,瀏覽器會自己設置數據緩存(當然這個緩存我們不想要)想要不走瀏覽器的緩存,我們需要保證每次請求的url都不完全一致:每次請求,問號傳參后加上隨機數或者時間戳'如:'./data.json?x=1&name=davina&_='+Math.random()。

  請求頭部為請求報文添加了附加信息,它是HTTP報文要素之一。HTTP首部字段是由首部字段名和字段值構成(名/值),中間用冒號“:” 分隔。每行一對,名和值之間使用冒號分隔。請求頭部的最后會有一個空行,表示請求頭部結束。

  接下來為請求數據,它可以分為五類:通用首部、請求首部、響應首部、實體首部和擴展首部。

  通用首部:通用首部可以在客戶端、服務器和其他應用程序之間提供一些非常有用的通用功能,下面是一些常見的通用首部信息:

    首部:Connection      描述:允許客戶端和服務器指定與請求/響應連接有關的選項,如Connection:Keep-Alive

    首部:Date                 描述:提供日期和時間標志,說明報文是什么時間創建的

    首部:MIME-Version  描述:給出了發送端使用的MIME版本

    請求首部:只在請求報文中有意義的首部。用於說明是誰或什么在發送請求、請求源自何處等等信息。常見的有以下:

    首部:From                描述:提供了客端用戶的地址

    首部:Host                 描述:給出了接收請求的服務器的主機號和端口名

    首部:User-Agent      描述:將發起請求的應用程序名稱告知服務器

  響應首部:響應報文有自己的響應首部集。響應首部為客戶端提供了一些額外信息

  實體首部:實體首部提供了有關實體及其內容的大量信息,實體首部可以告知報文的接收者它在對什么進行處理。

  擴展首部:HTTP首部字段是可以自行擴展的。所以在Web服務器和瀏覽器的應用上,會出現各種非標准的首部字段。 

  2、編碼和解碼

  當客戶端和服務器端進行通信時如果出現了中文則需要進行編碼和解碼。JS的編碼和解碼有以下三種方式:

  encodeURI()/decodeRUI(): 這兩個函數把字符串作為URI進行編碼/解碼,實際上encodeURI()函數只把參數中的空格編碼為%20,漢字進行編碼,其余特殊字符不會轉換。

    <script> let str = "www.baidu.com/davina /微信"; console.log(encodeURI(str)); //www.baidu.com/davina%20/%E5%BE%AE%E4%BF%A1
 let str1 = "www.baidu.com/davina /微信/ac1"; console.log(encodeURI(str1)); //www.baidu.com/davina%20/%E5%BE%AE%E4%BF%A1/ac1
    </script>

  encodeURIComponent()/decodeURIComponent(): 這兩個函數可把字符串作為URI組件進行編碼/解碼。由於這個方法對:/都進行了編碼,所以不能用它來對網址進行編碼,而適合對URI中的參數進行編碼/解碼。

    <script> let uri = "https://www.davina/com/from=http://wwws.baidu.com"; console.log(encodeURIComponent(uri)); //https%3A%2F%2Fwww.davina%2Fcom%2Ffrom%3Dhttp%3A%2F%2Fwwws.baidu.com //所以一般我們的用法是:
      let newUri = `https://www.davina/com/from=${encodeURIComponent(
        "https://www.baidu.com" )}`; console.log(newUri); //https://www.davina/com/from=https%3A%2F%2Fwww.baidu.com
    </script>

  escape()/unescape(): 函數對字符串進行編碼/解碼,將字符的unicode編碼轉化為16進制序列。它也是可以解碼中文的。用於服務端與服務端傳輸多。它不對/進行編碼。

    <script> let uri = "https://www.davina/com/from=http://wwws.baidu.com"; console.log(escape(uri)); //https%3A//www.davina/com/from%3Dhttp%3A//wwws.baidu.com
    </script>

  二、緩存檢查

  所謂的瀏覽器緩存就是瀏覽器將用戶請求過的資源存儲到本地電腦。當瀏覽器再次訪問時就可以直接從本地進行加載,不需要去服務端進行請求。它減少不必要的數據傳輸,減少服務器負擔擔升網站性能,提高了客戶端網頁的打開速度。一般分為強緩存和協議緩存。

  瀏覽器對於強緩存的處理是根據第一次請求資源時返回的響應頭來確定的,它不會再向服務器發送請求,直接從緩存中讀取資源。在chrome控制台中我們可以看到有Expires或者是cache-Control它們都是服務器設置並且是基於響應頭信息返回給客戶端的信息。是用來指定資源到期的時間,緩存過期時間。如果二者同時存在則cache-Control的優先級高於Expires。是否走緩存http的狀態碼都是200。所以對於強緩存來說,當客戶端已經緩存信息而服務器資源文件進行更新這時用戶就不能及時獲取到服務器最新的資源信息了。

  而協商緩存這種緩存機制客戶端需要和服務器端進行協商,它是在強緩存失效的情況下才觸發的一種機制。在了解協商緩存前我們先要了解以下幾個名詞:

  Last-Modify/If-Modify-Since:瀏覽器第一次請求資源文件時,服務器返回的header中會加上Last-Modify它是一個時間標識,標識着這個資源最后的修改時間,當瀏覽器再次請求該資源時request請求頭中會包含If-Modify-Since這個值為緩存之前返回的Last-Modify。服務器收到If-Modify-Since后會根據資源的最后修改時間判斷是否命中緩存

  Etag: web服務器響應請求時,它會告訴瀏覽器當前資源在服務器的唯一標識。Etag要優於Last-Modified。Last-Modified的時間單位是秒,如果某個文件在1秒中改變了很多次,那么它的Last-Modified其實並沒有體現出來修改,但是Etag每次都會改變確保了精准度。但是Etag在性能上要遜於Last-Modified的,服務器校驗會優先考慮Etag。

  If-None-Match: 當資源過期時發現資源有Etag聲明,則再次向web服務器請求時帶上If-None-Match(Etag值)。web服務器收到請求后發現有If-None-Match則與被請求資源的相應校驗串進行比較決定是否命中協商緩存。

  所以由上可以看出瀏覽器緩存過程如下:

  1、當我們向服務器第一次發送請求時,因為沒有緩存所以直接從服務器上獲取信息,服務器返回200,並把response header及請求的返回時間一並緩存,客戶端拿到內容后把信息和標識緩存到本地;

  2、當我們再次發送請求瀏覽器它會首先檢測本地是否有緩存或者是緩存是否過期,如果沒有過期則直接基於緩存信息進行渲染,如果時間過期則向服務器發送header帶有If-none-Match和If-Modified-Since的請求;

  3、服務器接收到請求后優先根據Etag的值判斷被請求的文件有沒有做修改,Etag值一致沒有修改,命中協商緩存返回304如果不一致則說明有修改直接返回新的資源文件帶上新的Etag值並返回200;

  4、如果服務器收到的請求中沒有Etag則將If-Modified-Since和被請求文件的最后修改時間做比較,如一致則命中協商緩存返回304,不一致則返回新的last-Modified和文件並返回200。 

  三、DNS解析

   域名是一串用點分隔的數字組成的internet上某一台計算機或者計算機組的名稱。簡單來說它可以理解成為通向某個網站的路。由於IP地址不方便記憶所以人們設計出了域名,並通過DNS將域名和IP地址相互映射,這樣我們可以更加方便的快速的訪問互聯網了。只有頂級的域名是需要花錢進行購買的。

  DNS(Domain Name System): DNS服務它是一種提供域名到ip地址之間的解析服務。計算機既可以被賦予ip地址,也能被賦予主機名和域名。它解析有以下幾步組成:

  查找本地是否有緩存,如果有則本地解析,本地解析的話瀏覽器先檢查自身中有沒有緩存,如果有,解析結束。如果沒有,則 查看c盤hosts文件,如果有瀏覽器會首先使用這個ip地址。如果本地hosts文件里沒有,則請求本地DNS解析器,解析這個過程。

  如果本地沒有緩存,從根域名服務器,頂級域名服務器,權威域名服務器上進行查找,把查找到的結果返回給本地DNS解析器,返回給客戶端。典型一次DNS解析需要花費20~120毫秒,這樣就很浪費時間。

  所以我們需要對DNS解析進行優化,進行DNS預解析(DNS Prefetch)。也就是說根據瀏覽器定義的規則利用link的異步加載在GUI線程渲染頁面的同時去解析DNS並把解析結果緩存到系統緩存,當GUI渲染到某部分需要請求外部資源這時我們已經解析好了這樣可以縮短DNS解析時間來提高網站的訪問速度。

  當代產品的開發資源一般都是部署到不同服務器上,尤其對於大型項目而言,可以分為web資源服務器,圖片資源服務器,數據接口服務器,第三方服務器......。這樣分開部署可以對服務器資源進行分配,在每台服務器並發有上限的情況下,這樣部署可以提高並發,每一個源下可以同時允許的HTTP並發數是6~7個,這樣分開部署有助於提高頁面的渲染速度。

  DNS預解析的具體方法如下:

 //用meta信息來告知瀏覽器, 當前頁面要做DNS預解析
<meta http-equiv="x-dns-prefetch-control" content="on">
//在頁面header中使用link標簽來強制對DNS預解析:
<link rel="dns-prefetch" href="//www.xxx.com">

   四、建立TCP通道

  瀏覽器通過DNS獲取到web服務器真的IP地址后,便向服務器發起TCP連接請求,通過TCP的三次握手建立好連接后,瀏覽器便可以將http請求數據通過發送給服務器了。那什么是TCP?為什么需要握手?為什么握手不是兩次,或者是4次而是三次?

     TCP是可靠通信協議,接收方收到的是完整,有序,無差錯的數據。可靠性高,應用於傳輸大量數據對可靠性要求高的場合。與TCP相對的是UDP它是不可靠通信協議,接收方接收到的數據可能存在部分的丟失,順序也不一定能保證,但是它是的傳輸速度快。

  TCP和UDP協議它們都是基於同樣的互聯網基礎設施,基於IP協議來實現的,互聯網基礎設施中對於數據包的發送過程是會發生丟包現象的而TCP協議為了實現可靠傳輸引入了序號(sequence number)

和確認號(acknowledgement number)確保了通信雙方會判斷自己發送的數據包對方是否收到,如果沒有收到則重發,而UDP則做不到。

  發送方要發送數據包時,同時送一個序號,那么接收方收到這個數據包后,就可以回復一個確認號,告訴發送方“我已經收到了你的數據包,你可以發送下一個數據包,序號從某某開始”。

  為什么是三次而不是兩次?為了實現可靠傳輸,發送方和接收方始終需要同步序列編號(Synchronize Sequence Numbers)。 需要注意的是, 序號並不是從 0 開始的, 而是由發送方隨機選擇的。

初始序列號 ( Initial Sequence Number, ISN )開始 。 由於 TCP 是一個雙向通信協議, 通信雙方都有能力發送信息, 並接收響應。 因此, 通信雙方都需要隨機產生一個初始的序列號, 並且把這個起始值告訴對方。

  所以TCP的三次握手如下:

  第一次握手:建立連接時,客戶端發送syn包(syn=x)到服務器,並進入SYN_SENT(Synchronize Sequence Numbers)狀態,等待服務器確認。(白話文:你好啊,服務器,我要發數據啦,先出一個題目考一下你喲,對一下暗號。)

  第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=x+1),同時自己也發送一個SYN包(syn=y),即SYN+ACK包,此時服務器進入SYN_RECV狀態。(白話文:你好啊,瀏覽器我准備好啦,你可以發呢,題目已經做好了我也出題考考你啦)

  第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=y+1),此包發送完畢,客戶端和服務器進入ESTABLISHED(TCP連接成功)狀態,完成三次握手。(白話文:你好啊,服務器,我馬上要發數據啦,你准備接收吧,做出題目返回給服務器)

  五、服務器處理請求,返回響應結果,數據傳輸

  服務器接收到請求的數據后,會根據端口號請求資源的路徑名稱找到資源文件並讀取文件中的內容,把內容返回。所有經過傳輸協議,服務器返回給客戶端的內容,都被成為響應報文。它也有三部分組件:HTTP狀態碼,響應頭和響應主體。

  狀態碼:HTTP狀態碼負責表示客戶端HTTP請求的返回結果、標記服務器端的處理是否正常、通知出現的錯誤等工作。HTTP狀態碼被分成了五大類,不同的類型代表不同類別的狀態碼。

  【1XX】: Informational(信息性狀態碼) 表示接收的請求正在處理,一般看不到

  【2XX】:Success(成功狀態碼) 表示請求正常處理完畢。代表:200(成功),204(對於某些請求,服務器不想處理返回空和這個碼)

       【3XX】:Redirection(重定向狀態碼) 表示需要進行附加操作以完成請求。如:301(永久轉移),307(臨時重定向Temporary Redirect =>主要用於:服務器的負載均衡)

  【4XX】 :Client Error(客戶端錯誤狀態碼) 表示服務器無法處理請求。如:400(參數錯誤),404(請求地址錯誤)

  【5XX】 :Server Error(服務器錯誤狀態碼) 表示服務器處理請求出錯。如:500(未知服務器錯誤),503(服務器超負荷)

  六、關閉TCP連接通道(四次揮手)

  第一次揮手: 首先從客戶端開始發出連接釋放報文,並且停止發送數據,這客戶端進入到終止等待1狀態。(白話文:你好啊服務器,我請求報文發送完啦,你准備關閉吧)

  第二次揮手:從服務器到客戶端 。服務器收到連接釋放報文,發出確認報文且帶上自己的序列號,這時服務器端進入了關閉等待狀態。(白話文:你好啊瀏覽器,我接收完請求報文准備關閉啦,你也准備吧)

  這時TCP服務器要通知高層的應用進程,客戶端向服務器釋放,這時服務器處於半關閉狀態。如果服務器還有數據要發送,客戶端依然要接受。這個狀態還要持續一段時間,也就是整個CLOSE-WAIT狀態持續的時間。

  客戶端收到服務器的確認請求后,此時,客戶端就進入FIN-WAIT-2狀態,等待服務器發送連接釋放報文(在這之前還需要接受服務器發送的最后的數據

  第三次揮手:服務器將最后的數據發送完畢后,就向客戶端發送連接釋放報文,由於在半關閉狀態,服務器很可能又發送了一些數據,服務器就進入了最后確認狀態,等待客戶端的確認。(白話文:你好啊瀏覽器,我響應報文發送完啦,你准備關閉吧)

  第四次揮手:客戶端收到服務器釋放報文后,發出確認此時,客戶端就進入了時間等待狀態。因為此時TCP連接還沒有釋放,必須經過一段時間2∗∗MSL(最長報文段壽命),因為網絡是不可靠的,有可以最后一個ACK丟失。所以時間狀態就是用來重發可能丟失的ACK報文的。當客戶端撤銷相應的TCB后,才進入CLOSED狀態。(白話文:你好啊服務器,我響應報文接收完畢了,我准備關閉了,你也准備吧)
  服務器只要收到了客戶端發出的確認,立即進入CLOSED狀態。同樣,撤銷TCB后,就結束了這次的TCP連接。服務器結束TCP連接的時間要比客戶端早一些。

  所以我們可以看到當服務器端收到客戶端的SYN連接請求報文后,可以直接的發送請求連接和確認連接,但在關閉的時候,服務器收么到關閉信息后,很可能不會立即關閉,它需要時間來反應,只能先回復一個確認,只有當服務器端所有的報文都發送完后,服務器端才能發送希望斷開連接。所以需要揮手需要四步來完成。

  六、客戶端渲染

  不同的瀏覽器內核不同,所以渲染過程不太一樣。但大體上瀏覽器渲染進程包含解析HTML文件和CSS文件、加載圖片資源文件,執行解析js文件腳本代碼等內容。整個過程瀏覽器會開啟多個線程協作完成。

  瀏覽器是專門用來訪問和瀏覽網頁的客戶端軟件。在深入了解瀏覽器的渲染機制前提下,我們首先要知道幾個知識點,如什么是線程?什么是進程?瀏覽器是由什么組成的?

  進程:操作系統分配的占有CPU資源的最小單位,它有獨立的地址空間。

  線程:安排CPU執行的最小單位。同一個進程下可以有多個線程,它們是共享進程的地址空間。可以簡單來看,計算機工廠,線程是大車間,而線程是大車間是不同的部門。需要注意的是這些部門是沒有層級關系的,它們之間相互工作。

  瀏覽器是多進程的,瀏覽器的渲染進程是多線程的。瀏覽器常用的線程有:GUI渲染線程,JavaScript引擎線程,定時器觸發線程,事件觸發線程,異步請求線程等等。

  瀏覽器的組成如下圖所示:

  一、渲染流程

  從資源的下載到最終的頁面展現,它的中間依次經過以下幾部分:

  webkit引擎渲染的詳細流程如下圖所示:

  1. 構建DOM TREE

    解析html文檔,生成DOM樹。

   a. 解析html。html語法樹分為兩個部分:詞法解析和語法解析。

      詞法解析按照詞法規則來進行,將html文本分割成大量的標記(Token)

      語法解析按照語法規則匹配Token生成語法樹,但瀏覽器內核中對html頁面真正的內部表示並不是語法樹,而是w3c規范的DOM.DOM也是樹形的結構,它的節點基本和html語法樹節點一一對應,所以在語法解析過程中,最后直接生成了DOM樹。

   b. 解析css。頁面中所有的CSS由CSS樣式表(CSSStyleSheet)集合而成。一個 CSS 樣式表包含了一組表示規則的 CSSRule 對象。每一條CSSRule則由選擇器部分和聲明部分構成,而聲明部分是CSS屬性和值的Key-Value集合。css解析完畢后會進行CSSRule的匹配過程,尋找滿足每條CSS規則Selector部分的HTML元素,然后將其Declaration部分應用於該元素。實際規則匹配過程會考慮到默認和繼承的CSS屬性、規則的優先級等因素。

   c. 解析javascript。它一般是由單獨的腳本引擎來解析執行,通常是動態的改變DOM樹。

  2. 構建RENDER TREE

  渲染樹(Render Tree)它可以和DOM樹一一對應,二者在內核中同時存在,作用不同。DOM樹是html文檔對象表示,同時也是JS操作html的接口,Render Tree是DOM Tree樹的排版表示,用以計算可視DOM節點的布局信息和后續階段的繪制顯示。要注意一點,並不是所有的DOM節點都可視,也就是說並不是所有DOM Tree節點都會對應生成一個Render Tree節點(如head標簽)。同時,DOM Tree可視節點的CSS 樣式就是其對應Render Tree節點的樣式。總結來說就是用dom tree和cssom tree構造render tree。

  3. 布局RENDER TREE(layout)

  布局RENDER TREE首先要計算布局。布局是安排和計算頁面中每個元素大小位置的過程。這個過程就是通過Render Tree中渲染的對象信息,計算出每一個渲染對象的位置和尺寸,將它放在瀏覽器窗口正確的。

  4. 繪制RENDER TREE(paint)

    Paint模塊負責將Render Tree映射成可視的圖形,它會遍歷Render Tree調用每個Render節點的繪制方法將其內容顯示在一塊畫布或者位圖上,並最終呈現在瀏覽器應用窗口中成為用戶看到的實際頁面。

    注意:reflow & repaint

      有時文檔布局完成后我們會對dom進行修改,這時可能會重新進行布局,可以稱其為回流(reflow)或者是重排(relayout)。由於html主要使用的是流式布局,如果頁面中的一個元素尺寸發生了變化,那后續的元素位置都要跟着變化,要重新進行布局。觸發回流的方式:窗口尺寸被修改,發生滾動等等。

      每個頁面至少需要一次回流,那就是在頁面第一次加載的時候。回流發生在Render Tree上。我們通常所說的脫離文檔流就是指脫離Render Tree.

      重繪是指當與視覺相關的樣式屬性值被更新時會觸發的繪制過程(只是影響元素的外觀,風格不會影響布局),在繪制過程中要重新計算元素的視覺信息,使元素呈現新的外觀。

      回流一定會引起重繪,重繪不一定會引起回流。

      雖然回流是必不可少的一步,無法避免,但我們要避免多次的回流與重繪。有以下方法:

        a.不要一條條的修改dom樣式,樣式集中改變;

        b.對一個元素進行復雜操作時可以先隱藏,操作完再顯示;

        c.需要經常獲取那些引起回流的屬性值進,要緩存到變量中;

        d.不要使用table布局,分離讀寫操作;

        e.放棄傳統操作DOM開始使用vue或者是react,我們自己不操作DOM,只操作數據,讓框架幫我們根據數據渲染視圖等

 

   當服務器返回一個html文件給瀏覽器,瀏覽器接受到的是一些字節數據,它會根據http響應中的編碼方式(通常是utf-8)解析字節數據,得到代碼字符串,然后按照w3c的規則進行字符解析,生成對應的tokens,接着瀏覽器再使用這些tokens創建對象,轉換為瀏覽器內核可以識別和渲染的DOM節點。這時html解析器就會從自上而下遍歷這些節點,節點如果是普通節點html解析器就將其到dom樹中,如果是css代碼就交把css解析器,如果節點是js代碼html就會交給js解析器。節點最后解析為對應的dom tree 和 cssom tree。把dom tree和cssom tree結構合在一起,生成有結構的render tree。最后瀏覽器按照render tree在頁面中進行渲染和解析呈現在瀏覽器應用窗口中成為用戶看到的實際頁面。

  正常情況下我們可以看到js會阻礙GUI的渲染,所以一般js放在頁面的尾部,確保DOM Tree生成完才會去加載js。我們也可以用defer或者是async異步管控js的請求

  defer:屬性表示延遲執行引入js,js加載時html並未停止解析,這兩個過程是並行的。整個 document 解析完畢且 defer-script 也加載完成之后(這兩件事情的順序無關),會執行所有由 defer-script 加載的 JavaScript 代碼,再觸發 DOMContentLoaded事件(初始的 HTML 文檔被完全加載和解析完成之后觸發,無需等待樣式表圖像和子框架的完成加載) 

  async屬性表示異步執行引入js,與 defer 的區別在於,如果已經加載好,就會開始執行,無論此刻是 html解析階段還是 DOMContentLoaded 觸發之后。需要注意的是,這種方式加載的js依然會阻塞 load 事件。換句話說,async-script 可能在 DOMContentLoaded 觸發之前或之后執行,但一定在 load 觸發之前執行。多個 async-script 的執行順序是不確定的,誰先加載完誰執行。

  總結:瀏覽器的渲染進程是多線程的。瀏覽器常用的線程有:GUI渲染線程,JavaScript引擎線程,定時器觸發線程,事件觸發線程,異步請求線程等等。

  GUI渲染線程:負責渲染瀏覽器界面,解析html,css,構造DOM Tree ,Render Tree,layout,print等等。

  JavaScript引擎線程:負責解析js代碼,如v8引擎。它和GUI渲染線程是互相排斥的,當JS引擎執行時,GUI會被掛起,GUI更新會被保存在一個隊列中等到js引擎空閑時執行,所以JS執行時間不能過長,不然會造成頁面的渲染的不連貫。

  事件觸發線程:處理DOM事件,歸屬於瀏覽器,用來控制事件循環,當相應的事件符合觸發條件時,這個線程會把事件添加到等待隊列的隊尾進行處理

  定時器觸發線程:setTimeout和setInterval所在的線程,它們是通過單獨線程來計時並觸發定時的。

  異步http請求線程:處理http請求

  渲染進程如下:

  打開一個瀏覽器可以看到任務管理器出現兩個進程,一個是主線程,一個是打開Tab頁的渲染進程。主進程收到用戶請求,首先要獲取頁面的內容,將這個任務通過RenderHost接口傳遞給Render渲染進程,Render渲染渲染進程收到消息后,交給GUI渲染線程,開始渲染、GUI渲染線程接收到請求后,加載渲染頁面,這其中有可能會需要主進程來獲取資源,會有JS線程操作DOM等等。最后Render渲染進程將結果傳遞給主進程,主進程接收結果並繪制。

  所以說,當瀏覽器輸入一個url后,首先要在客戶端上進行url解析,在DNS服務器上進行DNS解析,建立tcp連接通道后瀏覽器向服務器發送http請求,服務器把結果返回給瀏覽器,返回要關閉tcp連接通道,然后在瀏覽器上把獲取的結果進行渲染。瀏覽器內核拿到相應內容后,渲染開始,解析html,css分別建立DOM Tree和CSSOM Tree,隨后這兩個合並成Render Tree。然后布局Render Tree,繪制Render Tree,繪制頁面像素信息。瀏覽器會將各層的信息發送給GPU,GPU會將各層合成,顯示在屏幕上。渲染完成。

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM