目錄css
大多數URL的結構組成:<scheme>://<host>:<port>/<path>;<params>?<query>#<frag>
,更詳細查詢Wikipedia - URL,維基百科 - 統一資源定位符html
除了合法的格式,瀏覽器還會檢查字符是否合法。URL 爲了在不一樣協議不一樣傳輸機制均可以安全的運送信息,採用的字符都是符合 ASCII 集的。這其中還包含一些保留字符,好比上面的 URL 協議中的分割字符,等特殊含義的字符。前端
而不安全的字符,非 ASCII 的 Unicode(中文等) 字符,就會經過轉義去處理,使用 % 。html5
此部分就是檢查 url 的合法性,並對不合法的進行轉義。提取出 domain name 以後,就該是 DNS 解析相關的了,不過還要提一下,瀏覽器還會檢查 HSTS 列表。git
HSTS 是一種安全策略的機制,是爲了讓瀏覽器強制使用 HTTPS 訪問網站。當你的網站均採用 HTTPS,並符合它的安全規範,就能夠申請加入 HSTS 列表,以後用戶不加 HTTPS 協議再去訪問你的網站,瀏覽器都會定向到 HTTPS。詳細請看HSTS 詳解。github
即 DNS 過程,步驟以下:web
查看瀏覽器內部緩存
檢測域名是否存在於瀏覽器緩存中,若是有緩存直接使用,沒有則下一步。打開 chrome://net-internals/#dns
便可查看本機瀏覽器的 DNS 緩存。算法
系統緩存
瀏覽器會調用一個相似 gethostbyname
的庫函數,此函數會先去檢測本地 hosts 文件,查看是否有對應 ip。chrome
PS: 這裏有一個點,localhost 默認 ip 是 172.0.0.1,這是一個迴路段,也叫換回接口。也就是不會發往服務器,是直接在本地打開的。後端
路由器緩存、ISP 緩存
若是瀏覽器和系統緩存都沒有,系統的 gethostname 函數就會向 DNS 服務器發送請求。而網絡服務通常都會先通過路由器以及網絡服務商(電信),因此會先查詢路由器緩存,而後再查詢 ISP 的 DNS 緩存。
本地 DNS 服務器
一般爲本身計算機搭建的小型 DNS 服務器,自我使用,屬於 DNS 優化的一部分。
域名服務器
到此處的過程爲:根域服務器(.) -> 頂級域名服務器(eg: .com,.org)->
主域名服務器(eg: google.com)
若是域名正常,應該就會返回 IP 地址,若是沒有瀏覽器就會提示找不到服務器地址。
DNS 優化
DNS 查詢的過程經歷了不少的步驟,若是每次都如此,是否是會耗費太多的時間,資源。因此咱們應該儘早的返回真實的 IP 地址,減小查詢過程,也就是 DNS 緩存。
瀏覽器獲取到 IP 地址後,通常都會加到瀏覽器的緩存中,本地的 DNS 緩存服務器,也能夠去記錄。另外,天天幾億網名的訪問需求,一秒鐘幾千萬的請求域名服務器如何知足?就是 DNS 負載均衡。
一般咱們的網站應用各類雲服務,或者各類服務商提供相似的服務,由他們去幫咱們處理這些問題。DNS 系統根據每臺機器的負載量,地理位置的限制
(長距離的傳輸效率)等等,去提供高效快速的 DNS 解析服務。
TCP 三次握手
IP 和端口號可使 TCP 鏈接對應的服務器,網絡信號經由網線傳輸到對應服務器的網絡調節器後,再轉化成數字信號,使計算機能夠處理(發出和接收的過程是同樣的)。那是否是這樣就能夠創建 TCP 鏈接了呢?還不行。TCP 爲了創建可靠穩定的比特傳輸管道,會執行屢次。
創建鏈接是有名的 TCP 協議三次握手,通俗理解就是:
但爲何是「三次握手」?不是一次兩次四次?
爲何是三次握手
資料裏說的是爲了防止已失效的鏈接請求報文段忽然又傳送到了服務端,於是產生錯誤。失效的鏈接是怎樣一種狀況呢?咱們都經歷過網絡很差的時候,網絡中的延遲現象也是時有發生的。當客戶端發送的一個請求在網絡的某個地方停滯的時候,服務器端並不會感知到,延遲到必定時間就會發生超時現象,客戶端一般會斷開鏈接。而這時候停滯在途中的某個請求,又發送服務器了,假設不是約定「三次握手」,服務器認定客戶端此時要創建鏈接,便會佔用程序去監聽處理此鏈接,可是客戶端是徹底「無感」的,並沒有鏈接,就會致使鏈接「錯開」的現象。
一樣的,服務器像客戶端發請求也是同樣的,請求滯後也會致使這種現象。因此要彼此確認,再創建鏈接。
因此說,「三次握手」是很必要的。第一次 client 端發送給 server 端,server 確認了 client 的請求功能正常以及本身的接收監聽服務正常。第二次 server 再發送給 client,告訴 client 我已經收到了,client 端就能夠確認,個人請求是沒問題的,而且確認了 server 的請求功能正常。可是 server 並不知道本身請求是否成功了啊,因此 client 再發送一次請求,若是 server 一樣接收到,server 就能夠確認本身的請求是沒問題的。這樣,client,server 的收發才真正確認成功,通訊正常,鏈接能夠創建。
三次握手只能確認了雙方的收發通訊功能正常,要確保數據的正確穩定傳輸,還須要序列號、確認號等機制的輔助。
請求報文的格式: <method> <request-URL> <version> <headers> <entity-body>
請求報文的詳細說明見下一條。
響應報文的格式(注意,只有起始行的語法有所不一樣): <version> <status> <reason-phrase> <headers> <entity-body>
起始行和首部就是由行分隔的 ASCII 文本。每行都以一個由兩個字符組成的行終止序列做爲結束,其中包括一個回車符(ASCII 碼 13)和一個換行符(ASCII 碼 10)。 這個行終止序列能夠寫作 CRLF。
起始行(Start Lines)
全部的 HTTP 報文都以一個起始行做爲開始。請求報文的起始行說明了要作些什麼。
響應報文的起始行說明發生了什麼。
請求行(Request line)
請求報文請求服務器對資源進行一些操做。請求報文的起始行,或稱爲請求行,包含了一個方法和一個請求 URL,還包含 HTTP 的版本。全部這些字段都由空格符分隔。
響應行(Response line)
響應報文承載了狀態信息和操做產生的全部結果數據,將其返回給客戶端。響應報文的起始行,或稱爲響應行,包含了響應報文使用的 HTTP 版本、數字狀態碼,以 及描述操做狀態的文本形式的緣由短語。
方法(Methods)
請求的起始行以方法做爲開始,方法用來告知服務器要作些什麼。好比,在行 「GET /specials/saw-blade.gif HTTP/1.0」中,方法就是 GET。
序號 | 方法 | 描述 |
---|---|---|
1 | GET | 請求指定的頁面信息,並返回實體主體。 |
2 | HEAD | 相似於get請求,只不過返回的響應中沒有具體的內容,用於獲取報頭 |
3 | POST | 向指定資源提交數據進行處理請求(例如提交表單或者上傳文件)。數據被包含在請求體中。POST請求可能會致使新的資源的創建和/或已有資源的修改。 |
4 | PUT | 從客戶端向服務器傳送的數據取代指定的文檔的內容。 |
5 | DELETE | 請求服務器刪除指定的頁面。 |
6 | CONNECT | HTTP/1.1協議中預留給可以將鏈接改成管道方式的代理服務器。 |
7 | OPTIONS | 容許客戶端查看服務器的性能。 |
8 | TRACE | 回顯服務器收到的請求,主要用於測試或診斷。 |
狀態碼 (Status codes)
狀態碼是在每條響應報文的起始行中返回的。會返回一個數字狀態和一個可讀的狀態。數字碼便於程序進行差錯處理,而緣由短語則更便於人們理解。方法是用來告訴服務器作什麼事情的,狀態碼則用來告訴客戶端,發生了什麼事情。
總體範圍 | 已定義範圍 | 分類 |
---|---|---|
100 ~ 199 | 100 ~ 101 | 信息提示 |
200~299 | 200~206 | 成功 |
300 ~ 399 | 300 ~ 305 | 重定向 |
400 ~ 499 | 400 ~ 415 | 客戶端錯誤 |
500 ~ 599 | 500 ~ 505 | 服務器錯誤 |
更詳細請看菜鳥教程 - HTTP狀態碼。
緣由短語 (Reason phrases)
緣由短語是響應起始行中的最後一個組件。它爲狀態碼提供了文本形式的解釋。好比,在行 HTTP/1.0 200 OK 中,OK 就是緣由短語。
版本號(Version numbers)
版本號會以 HTTP/x.y 的形式出如今請求和響應報文的起始行中。
關於版本號更詳細請看:HTTP 協議各版本特性
首部
跟在起始行後面的就是零個、一個或多個 HTTP 首部字段。
HTTP 首部字段向請求和響應報文中添加了一些附加信息。本質上來講,它們只是一些名 / 值對的列表。
首部分類
HTTP 規範定義了幾種首部字段。應用程序也能夠隨意發明本身所用的首部。HTTP 首部能夠分爲如下幾類。
通用首部(General headers):既能夠出如今請求報文中,也能夠出如今響應報文中。
請求首部(Request headers):提供更多有關請求的信息。
響應首部(Response headers):提供更多有關響應的信息。
實體首部(Entity headers):描述主體的長度和內容,或者資源自身。
擴展首部(Extension headers):規範中沒有定義的新首部。
每一個 HTTP 首部都有一種簡單的語法:名字後面跟着冒號,而後跟上可選的空格,再跟上字段值,最後是一個 CRLF。
實體的主體部分(Entity Bodies)
HTTP 報文的第三部分是可選的實體主體部分。實體的主體是 HTTP 報文的負荷。 就是 HTTP 要傳輸的內容。HTTP 報文能夠承載不少類型的數字數據:圖片、視頻、HTML 文檔、軟件應用程 序、信用卡事務、電子郵件等。
四次揮手
TCP 數據傳輸完,是要斷開鏈接的。我說的數據傳輸完,是某一端發送完數據,要斷開鏈接,去發送一個斷開的請求,客戶端和服務端均可以主動斷開鏈接。爲何斷開要四次呢?
第一次是鏈接的某一端 A 請求斷開鏈接,發送報文段給 B,設置 seq = x 和 ackn = y,另外加一個標識位 FIN,表示已經沒有數據發送了,請求斷開。
B 收到請求,須要進行確認,即設置 ACK = 1,而後是 ackn = x(A 的 seq) + 1,B 的 seq 仍然是 y,只是確認收到 A 的關閉請求。
隔一段時間,B 再向 A 發送 FIN 報文段,請求關閉,FIN = 1,seq = y,ackn = x + 1。此時是最後確認階段。
A 收到 B 的 FIN,向 B 發送 ACK,確認關閉,seq = x + 1,ACK = 1,ackn = y + 1。發送完以後,A 會進入 TIME_WAIT 的階段,若是 B 收到 ACK 關閉鏈接,A 在 2MSL (報文最大生存時間)收不到 B 的響應就本身默認關閉了。
爲何斷開要四次
對比 TCP 創建鏈接的時候,區別大概就是第二步拆成了兩步。「三次握手」的時候確認 ACK 和同步 SYN 是一塊返回的,斷開鏈接則是分開發送,先發送 ACK 確認,再發送 FIN。這裏主要是由於 B 端是被動斷開的一方,A 發送完數據了,發送 FIN 表示我已經完事了,可是 B 不必定,也能還有數據會發送給 A。因此 B 會先 ACK 確認,而後當它真的沒有數據要發送了,纔會執行 FIN。
這種狀況主要是因爲 TCP 全雙工傳輸的特性決定的。什麼是全雙工?先說一下半雙工吧,舉個栗子,有一條很窄的道路,只有單通道,可是卻兩個方向的車均可以走。當有一個方向的車進入,另外一個方向的車就只能等待它經過才能進入。而全雙工就是互不影響,你走你的,我走個人。因此 TCP 的數據傳輸也是這樣,兩端同時能夠向對方發送數據,因此當 A 要斷開鏈接的時候,B 接收到 FIN 表示沒有數據會發來了,可是我還能夠繼續發送數據,可能還有數據要發,爲了數據不丟失,即採用先肯定後斷開的方式。
瀏覽器部分詳細可瀏覽:瀏覽器的工做原理:新式網絡瀏覽器幕後揭祕,很是詳細,這裏只作關鍵摘錄。
主流程
呈現引擎從網絡層獲取文檔內容之後,進入下面的主流程進行渲染。
呈現引擎將開始解析 HTML 文檔,並將各標記逐個轉化成「內容樹」上的 DOM 節點。同時也會解析外部 CSS 文件以及樣式元素中的樣式數據。HTML 中這些帶有視覺指令的樣式信息將用於建立另外一個樹結構:呈現樹。
呈現樹包含多個帶有視覺屬性(如顏色和尺寸)的矩形。這些矩形的排列順序就是它們將在屏幕上顯示的順序。
呈現樹構建完畢以後,進入「佈局」處理階段,也就是爲每一個節點分配一個應出如今屏幕上的確切座標。下一個階段是繪製 - 呈現引擎會遍歷呈現樹,由用戶界面後端層將每一個節點繪製出來。
須要着重指出的是,這是一個漸進的過程。爲達到更好的用戶體驗,呈現引擎會力求儘快將內容顯示在屏幕上。它沒必要等到整個 HTML 文檔解析完畢以後,就會開始構建呈現樹和設置佈局。在不斷接收和處理來自網絡的其他內容的同時,呈現引擎會將部份內容解析並顯示出來。
主流程示例:
Webkit 主流程
Gecko 呈現引擎主流程
這一步將文本的 HTML 文檔,提煉出關鍵信息,造成如圖的嵌套層級的樹形結構,便於計算拓展。
這一步的具體算法上文連接有詳細介紹,包括標記算法、樹構建算法、容錯機制等解釋。
CSS Parser 將 CSS 解析成 Style Rules,Style Rules 也叫 CSSOM(CSS Object Model)。
StyleRules 也是一個樹形結構,根據 CSS 文件整理出來的相似 DOM Tree 的樹形結構:
與 HTML Parser類似,CSS Parser 做用就是將不少個 CSS 文件中的樣式合併解析出具備樹形結構 Style Rules。
腳本處理
瀏覽器解析文檔,當遇到 <script>
標籤的時候,會當即解析腳本,中止解析文檔(由於 JS 可能會改動 DOM 和 CSS,因此繼續解析會形成浪費)。
若是腳本是外部的,會等待腳本下載完畢,再繼續解析文檔。如今能夠在 script
標籤上增長屬性 defer
或者 async
。
腳本解析會將腳本中改變DOM和CSS的地方分別解析出來,追加到 DOM Tree 和 Style Rules 上。
Render Tree的構建其實就是 DOM Tree 和 CSSOM Attach 的過程。
呈現器是和 DOM 元素相對應的,但並不是一一對應。Render Tree 實際上就是一個計算好樣式,與 HTML 對應的(包括哪些顯示,哪些不顯示)的 Tree。
樣式計算
構建呈現樹時,須要計算每個呈現對象的可視化屬性。這是經過計算每一個元素的樣式屬性來完成的。
樣式包括來自各類來源的樣式表、inline 樣式元素和 HTML 中的可視化屬性(例如「bgcolor」屬性)。其中後者將通過轉化以匹配 CSS 樣式屬性。
樣式計算存在如下難點:
樣式數據是一個超大的結構,存儲了無數的樣式屬性,這可能形成內存問題。
若是不進行優化,爲每個元素查找匹配的規則會形成性能問題。要爲每個元素遍歷整個規則列表來尋找匹配規則,這是一項浩大的工程。選擇器會具備很複雜的結構,這就會致使某個匹配過程一開始看起來極可能是正確的,但最終發現實際上是徒勞的,必須嘗試其餘匹配路徑。
Webkit 瀏覽器和 Firefox 瀏覽器經過共享樣式數據、構建規則樹、結構劃分等方法計算樣式,並最終和 Dom Tree 合併,獲得呈現樹。算法至關複雜,想詳細瞭解仍是請看連接。
漸進式處理
WebKit 使用一個標記來表示是否全部的頂級樣式表(包括 @imports)均已加載完畢。若是在附加過程當中還沒有徹底加載樣式,則使用佔位符,並在文檔中進行標註,等樣式表加載完畢後再從新計算。
呈現器在建立完成並添加到呈現樹時,並不包含位置和大小信息。計算這些值的過程稱爲佈局或重排。
HTML 採用基於流的佈局模型,這意味着大多數狀況下只要一次遍歷就能計算出幾何信息。處於流中靠後位置元素一般不會影響靠前位置元素的幾何特徵,所以佈局能夠按從左至右、從上至下的順序遍歷文檔。可是也有例外狀況,好比 HTML 表格的計算就須要不止一次的遍歷。
佈局是一個遞歸的過程。它從根呈現器(對應於 HTML 文檔的 <html>
元素)開始,而後遞歸遍歷部分或全部的框架層次結構,爲每個須要計算的呈現器計算幾何信息。
佈局的方式有全量佈局和增量佈局,全量佈局指觸發了整個呈現樹範圍的佈局,增量佈局指只對 Dirty 呈現器佈局。全量佈局每每是同步的,增量佈局每每是異步的。
佈局處理
佈局一般具備如下模式:
在繪製階段,系統會遍歷呈現樹,並調用呈現器的「paint」方法,將呈現器的內容顯示在屏幕上。繪製工做是使用用戶界面基礎組件完成的。
全局繪製和增量繪製
和佈局同樣,繪製也分爲全局(繪製整個呈現樹)和增量兩種。在增量繪製中,部分呈現器發生了更改,可是不會影響整個樹。更改後的呈現器將其在屏幕上對應的矩形區域設爲無效,這致使 OS 將其視爲一塊「dirty 區域」,並生成「paint」事件。OS 會很巧妙地將多個區域合併成一個。在 Chrome 瀏覽器中,狀況要更復雜一些,由於 Chrome 瀏覽器的呈現器不在主進程上。Chrome 瀏覽器會在某種程度上模擬 OS 的行爲。展現層會偵聽這些事件,並將消息委託給呈現根節點。而後遍歷呈現樹,直到找到相關的呈現器,該呈現器會從新繪製本身(一般也包括其子代)。
動態變化
在發生變化時,瀏覽器會盡量作出最小的響應。所以,元素的顏色改變後,只會對該元素進行重繪。元素的位置改變後,只會對該元素及其子元素(可能還有同級元素)進行佈局和重繪。添加 DOM 節點後,會對該節點進行佈局和重繪。一些重大變化(例如增大html
元素的字體)會致使緩存無效,使得整個呈現樹都會進行從新佈局和繪製。
瀏覽器對上文介紹的關鍵渲染路徑進行了不少優化,針對每一次變化產生儘可能少的操做,還有優化判斷從新繪製或佈局的方式等等。
在改變文檔根元素的字體顏色等視覺性信息時,會觸發整個文檔的重繪,而改變某元素的字體顏色則只觸發特定元素的重繪;改變元素的位置信息會同時觸發此元素(可能還包括其兄弟元素或子級元素)的佈局和重繪。
某些重大改變,如更改文檔根元素的字體尺寸,則會觸發整個文檔的從新佈局和重繪,據此及上文所述,推薦如下優化和實踐:
HTML文檔結構層次儘可能少,最好不深於六層;
涉及多域名的網站,能夠開啓域名預解析。
本部分摘自知乎 - 極樂科技專欄 - 李銀成 - Effective 前端7:加快頁面打開速度,本博客只摘錄優化方法和結論,原文有十分具體的分析過程可供學習、理解。
(1) 避免head標籤JS堵塞
全部放在head標籤裏的css和js都會堵塞渲染。若是這些CSS和JS須要加載和解析好久的話,那麼頁面就空白了。
有兩種解決辦法,第一種是把script放到body後面,這也是不少網站採起的方法。
第二種是給script加defer的屬性,defer是html5新增的屬性。一旦script是defer延遲的,那麼這個script將會異步加載,但不會立刻執行,會在readystatechange變爲Interactive後按順序依次執行。
defer的腳本會正常加載,可是延後執行,在interactive後執行,因此不會block頁面渲染。所以放在head標籤的script能夠加一個defer,可是加上defer的腳本發生了重大的變化,不可以影響渲染DOM的過程,只能是在渲染完了,例如綁的click事件在整個頁面沒渲染好以前不能生效。而且不少人要把它寫在head裏面,是爲了在頁面中間的script能調用到這些庫,影響DOM的渲染,加上defer就違背本意了。可是把script寫在head標籤始終是不推薦的,因此在頁面中間的script要麼使用原生的API,要麼把一些用到的函數寫成head標籤裏面的內聯script。
head標籤裏面的defer腳本跟放在body後面的腳本有什麼區別呢,區別在於defer的腳本會立刻加載,一旦頁面interactive了即可以立刻執行,而放body後面是document快interactive的時候纔去加載。咱們知道,瀏覽器同時加載資源數是有限的,例如Chrome對同一個域名的資源,每次最多同時只能創建6個TCP鏈接(讀者能夠試試)。因此寫在head標籤裏面的外鏈腳本會影響頁面圖片的加載,特別是當腳本不少時。所以,若是頁面的交互比圖片的展現更重要,那麼把script寫在head標籤加上defer是可取的,若是頁面的展現更爲重要,那麼應該把腳本放在body後面。
另外,defer可能會有兼容性問題,在老的瀏覽器上某些行爲表現可能會不一致。
一樣地,head標籤裏面的CSS資源也會block頁面渲染。
(2) 減小head標籤裏的CSS資源
因爲CSS必需要放在head標籤裏面,若是放在body裏面,一旦加載好以後,又會對layout好的dom進行重排,樣式可能又會發生閃爍。可是一旦放在head標籤裏面又會堵塞頁面渲染,若要加載好久,頁面就會保持空白狀態。因此要儘量地減小CSS的代碼量。
a) 不要放太多base64放在CSS裏面 b)把CSS寫成內聯的:
(1)使用響應式圖片
響應式圖片的優勢是瀏覽器可以根據屏幕大小、設備像素比ppi、橫豎屏自動加載合適的圖片,若是屏幕的ppi = 1的話則加載1倍圖,而ppi = 2則加載2倍圖,手機和mac基本上ppi都達到了2以上,這樣子對於普通屏幕來講不會浪費流量,而對於視網膜屏來講又有高清的體驗。
若是瀏覽器不支持srcset,則默認加載src裏面的圖片。
(2)延遲加載圖片
對於不少網站來講,圖片每每是佔據最多流量和帶寬的的資源。特別是那種瀑布式展現性的網站,一個頁面展現50本書,50張圖片,若是一口氣所有放出來,那麼頁面的Loaded時間將會較長,而且因爲並行加載資源數是有限,圖片太多會致使放body後面的js解析比較慢,頁面將較長時間處於不可交互狀態。因此不能一會兒把所有圖片都放出來,這對於手機上的流量也是不利的。
(1) gzip壓縮
使用gzip壓縮能夠大大減小文件的體積,一個180k的CSS文件被壓成了30k,減小了83%的體積。
(2) Cache-Control
(3) 使用etag
(1) DNS預讀取
域名解析可能會花很長的時間,而一個網站可能會加載不少個域的東西,例如使用了三個自已子域名的服務,再使用了兩個第三方的CDN,再使用了百度統計/谷歌統計的代碼,還使用了其它網站上的圖片,一個網站極可能要加載7、八個域的資源,第一次打開時,要作7、八次的DNS查找,這個時間是很是可觀的。所以,DNS預讀取技術可以加快打開速度,方法是在head標籤裏面寫上幾個link標籤:
<link rel="dns-prefection" href="https://www.google.com"> <link rel="dns-prefection" href="https://www.google-analytics.com"> <link rel="dns-prefection" href="https://connect.facebook.net"> <link rel="dns-prefection" href="https://googleads.g.doubleclick.net"> <link rel="dns-prefection" href="https://staticxx.facebook.com"> <link rel="dns-prefection" href="https://stats.g.doubleclick.net">
如上,對以上向個網站提早解析DNS,因爲它是並行的,不會堵塞頁面渲染。這樣能夠縮短資源加載的時間。
(2) html優化
把本地的html佈署到服務器上前,能夠先對html作一個優化,例如把註釋remove掉,把行前縮進刪掉。
(3) 代碼優化
對本身寫的代碼作優化,提升運行速度,例如說html別嵌套太多層,不然加劇頁面layout的壓力,CSS的選擇器別寫太複雜,否則匹配的計算量會比較大,對JS,別濫用閉包,閉包會加深做用域鏈,加長變量查找的時間。