以前面試時候常常被問及這個問題,支支吾吾回答沒有底氣,仔細研究了一下,發現裏面學問還真很多。css
從輸入 cnblogs.com 到博客園首頁徹底展示這個過程能夠大體分爲 網絡通訊 和 頁面渲染 兩個步驟。html
網絡通訊走的五層因特網協議棧(OSI標準是七層模型,但實際實現一般是五層)。畫了一張圖:前端
五層因特網協議棧webpack
DNS屬於應用層協議。客戶端會先檢查本地是否有對應的 ip 地址,若是有就返回,不然就會請求上級 DNS 服務器,知道找到或到根節點。這一過程可能會很是耗時,使用 dns-prefetch 可以使瀏覽器在空閒時提早將這些域名轉化爲 ip 地址,真正請求資源時就避免了這個過程的時間。例如京東首頁的處理:web
京東首頁dns-prefetch處理面試
HTTP也是應用層協議。HTTP(HyperText Transport Protocol)定義了一個基於請求/響應模式的、無狀態的、應用層的協議,用於從萬維網服務器傳輸超文本到本地瀏覽器。絕大多數的Web開發,都是構建在HTTP協議之上的Web應用。客戶端組織併發送 http 請求報文,包含 method、url、host、cookie 等信息,下面是訪問博客園首頁時 http 請求報文的樣子:瀏覽器
GET https://www.cnblogs.com/ HTTP/1.1 Host: www.cnblogs.com Connection: keep-alive User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36 Upgrade-Insecure-Requests: 1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: __gads=ID=b62b1e22b7de2e02:T=1493954370:S=ALNI_MYRebVRavER2PJmwdeFwpl33ACNoQ; If-Modified-Since: Mon, 27 Nov 2017 12:21:04 GMT
請求頭裏的每一個字段都有各自的做用,具體含義可查閱 http 協議相關文章。緩存
TCP 將 http 長報文劃分爲短報文,經過「三次握手」與服務器創建鏈接,進行可靠傳輸。「三次握手」創建鏈接的過程和打電話極像:性能優化
客戶端:喂,我要和 Server 通話
服務端:你好,我是 Server,你是 Client 嗎
客戶端:沒錯,我是 Client服務器
鏈接創建成功,接下來就能夠正式傳送數據了。
數據傳完以後斷開tcp鏈接還要經過「四次揮手」,大概意思以下:
客戶端:Server 小寶貝,我話說完了,你掛電話吧
服務端:我不掛,我不掛,你先掛,你不掛我也不掛
---------------- Client 一陣無語 --------------
服務端:你掛了嗎
客戶端:行,那我先掛了
至此完成了一次完整的資源請求響應。
須要注意的是,瀏覽器對同一域名下併發的tcp鏈接數是有限制的,2個到10個不等。爲了解決這個資源加載瓶頸,有幾種流行的優化方案:
好比頁面樣式所有打包在一個 css 文件內,頁面邏輯所有打包在一個 js 文件內,圖片拼合成雪碧圖,這樣可有效減小頁面的資源請求數量。webpack 是時下最流行的模塊打包工具之一,它能夠將頁面內全部資源(包括js,css,圖片,字體等等)都打包進一個 js 文件,不明覺厲。
當瀏覽器向服務器請求一個靜態資源時,會先發送該域名下的 cookies,服務器對於這些 cookie 根本不會作任何處理,所以它們只是在毫無心義的消耗帶寬,因此應該確保對於靜態內容的請求是無 cookie 的請求(也就是所謂的 cookie-free)。將站點的 js、css、圖片等靜態文件放在一個專門的域名下訪問,因爲該域名與主站域名不一樣,因此瀏覽器就不會把主域名下的 cookies 傳給該域,從而減小網絡開銷,特別是細碎靜態文件特別多的狀況下效果顯著。
另外一方面,因爲瀏覽器是基於域名的併發鏈接數限制,而不是頁面。所以將資源部署在不一樣的域名下可使頁面的總併發鏈接數獲得線性提高。
在 http 早期,每一個 http 請求都要打開一個 tcp 鏈接,請求完就關閉這個鏈接,致使每一個請求都要來一遍「三次握手」和「四次揮手」,從而磨磨唧唧多出來大量無謂的等待時間。就比如出去吃飯,等飯等半個小時,端上來十分鐘吃完了,結帳排隊又等了半個小時,要是剛進來就吃現成的吃完就跑那多爽啊。keep-alive 乾的就是這件事,當第一個請求數據傳輸完畢以後,服務器說「客戶端你不要關閉這個鏈接,直接換下個請求,我不想再握你的破手了」。這樣下個請求就直接傳輸數據而不用先走「三次握手」的流程了。這比如你又去吃飯,吃你最喜歡的紅燒肉,飯店在今天第一個客人點紅燒肉的時候就炒了一大鍋紅燒肉,你點餐的時候直接吃現成的就好了,吃完直接跑,哈哈美滋滋。
將靜態資源強制緩存在客戶端,經過添加文件指紋等方式使客戶端只請求發生了變動的資源,可有效下降靜態資源請求數量。具體可參看前端靜態資源緩存控制策略。
不少頁面瀏覽量雖然很大,但其實很大比例用戶掃完第一屏就直接跳走了,第一屏如下的內容用戶根本就不感興趣。 對於超大流量的網站,這個問題尤爲重要。這時可根據用戶的行爲進行按需加載,用戶用到了就去加載,用不到就不去加載。
以上都是從減小創建tcp鏈接數量的角度去優化頁面性能,以後會分享更多前端性能優化方面的實用方法。
Internet Protocol 是定義網絡之間彼此互聯規則的協議,主要解決邏輯尋址和網絡通用數據傳輸格式兩個問題。
全部鏈接到因特網上的設備都會被分配一個惟一的 IP 地址,就像網購時填寫的收貨地址同樣。因爲一個網絡設備的 IP 地址能夠更換,可是 MAC 硬件地址(就像身份證號)通常是固定不變的,因此首先使用 ARP 協議來找到目標主機的 MAC 硬件地址。當通訊的雙方不在同一個局域網時,須要屢次中轉(路由器)才能找到最終的目標,在中轉的過程當中還須要經過下一個中轉站的 MAC 地址來搜索下一個中轉目標。
傳輸層傳來的 TCP 報文會在這一層被 IP 封裝成網絡通用傳輸格式——IP數據包,IP 數據包是真正在網絡間進行傳輸的數據基本單元。
經過邏輯尋址定位到前面應用層 DNS 解析出來的 IP 地址的主機網絡位置,而後把數據以 IP 數據包的格式發送到那去。
數據鏈路層負責將 IP 數據包封裝成適合在物理網絡上傳輸的幀格式並傳輸。設計數據鏈路層的主要目的就是在原始的、有差錯的物理傳輸線路的基礎上,採起差錯檢測、差錯控制與流量控制等方法,將有差錯的物理線路改進成邏輯上無差錯的數據鏈路,向網絡層提供高質量的服務。當採用複用技術時,一條物理鏈路上能夠有多條數據鏈路。
上面這麼多層其實都是在爲不一樣的目的對要傳輸的數據進行封裝處理,而物理層則是經過各類傳輸介質(雙絞線,電磁波,光纖等)以信號的形式將上面各層封裝好的數據物理傳送過去。
至此一個 http 請求漂洋過海終於到達了服務器,接下來就是從物理層到應用層向上傳遞,將封裝的數據一層層剝開,服務器在應用層拿到最原始的請求信息後快速處理完,而後就開始向客戶端發送響應信息。此次是以服務器爲起點,客戶端爲終點再走一遍五層協議棧。
服務器的響應消息跋山涉水終於到達了瀏覽器,接下來就是頁面渲染(更具體可參看瀏覽器內部工做原理)。
頁面的渲染工做主要由瀏覽器的渲染引擎來完成(這裏以Chrome爲例)。
下面是渲染引擎在取得內容後的基本流程:
解析html構建dom樹 -> 解析css構建render樹 -> 佈局render樹 -> 繪製render樹
渲染引擎首先開始解析html,並將標籤轉化爲dom樹中的dom節點。接着,它解析外部css文件及style標籤中的樣式信息,這些樣式信息以及html標籤中的可見性指令將被用來構建另外一棵樹——render樹。render樹構建好了以後,將會執行佈局過程,該過程將肯定render樹每一個節點在屏幕上的確切座標。最後是繪製render樹,即遍歷render樹的每一個節點並將它們繪製到屏幕上。
偷了一張圖片(Chrome和Safari所用內核webkit頁面渲染主流程):
webkit頁面渲染主流程
爲了更好的用戶體驗,渲染引擎將會盡量早地將內容繪製在屏幕上,而不會等到全部的html都解析完成後再去構建、佈局和繪製render樹,它是解析完一部份內容就繪製一部份內容,同時可能還在經過網絡下載其他內容(圖片,腳本,樣式表等)。好比說,瀏覽器在代碼中發現一個 img 標籤引用了一張圖片,因而就向服務器發出圖片請求,此時瀏覽器不會等到圖片下載完,而是會繼續解析渲染後面的代碼,等到服務器返回圖片文件,因爲圖片佔用了必定面積,影響了後面段落的佈局,瀏覽器就會回過頭來從新渲染這部分代碼。
render樹節點和dom樹節點相對應,但這種對應關係不是一對一的,不可見的dom元素不會被插入render樹,例如head元素、script元素等。另外,display屬性爲none的元素也不會在渲染樹中出現(visibility屬性爲hidden的元素將出如今渲染樹中,這是由於visibility屬性爲hidden的元素雖然不可見但保留了元素的佔位)。
又偷了一張圖:
render樹與dom樹
當渲染對象被建立並添加到render樹後,它們並無位置和大小,計算這些值的過程稱爲layout(佈局)。
佈局的座標系統相對於根渲染對象(它對應文檔的html標籤,可用 document.documentElement
拿到),使用top和left座標。根渲染對象的位置是 (0,0),它的大小是viewport即瀏覽器窗口的可見部分。佈局是一個遞歸的過程,由根渲染對象開始,而後遞歸地經過一些或全部的層級節點,爲每一個須要幾何信息的渲染對象進行計算。
爲了避免由於每一個小變化都所有從新佈局,瀏覽器使用一個 dirty bit(頁面重寫標誌位)系統,一個渲染對象發生了變化或是被添加了,就標記它及它的children爲dirty——須要layout。
當layout在整棵渲染樹觸發時,稱爲全局layout,這可能在下面這些狀況下發生:
layout也能夠是增量的,這樣只有標誌爲dirty的渲染對象會從新佈局(也將致使一些額外的佈局)。增量layout會在渲染對象dirty時異步觸發,例如,當網絡接收到新的內容並添加到dom樹後,新的渲染對象會添加到render樹中。
繪製階段,遍歷render樹並調用渲染對象的paint方法將它們的內容顯示在屏幕上。和佈局同樣,繪製也能夠是全局的(繪製完整的樹)或增量的。在增量的繪製過程當中,一些渲染對象以不影響整棵樹的方式改變,改變的渲染對象使其在屏幕上的矩形區域失效(invalidate),這將致使操做系統將其看做dirty區域,併產生一個paint事件,操做系統很巧妙的處理這個過程,並將多個區域合併爲一個。
瀏覽器老是試着以最小的動做響應一個變化,因此一個元素顏色的變化將只致使該元素的重繪,元素位置的變化將致使元素的佈局和重繪,添加一個dom節點,也會致使這個元素的佈局和重繪。一些主要的變化,好比增長html元素的字號,將會致使緩存失效,從而引發整個render樹的佈局和重繪。
等到繪製完畢,頁面就徹底地展示在咱們面前了。
看似再簡單不過的操做,背後支撐的技術鏈已經複雜到不可想象。上面只是粗淺的輪廓,其中的每一步深挖進去都是一門大學問。不過我們前端了解一下就好了,不必較這個勁,否則就捨本逐末了。
以爲不錯就點個推薦吧:)