[每日一題]一道面試題是如何引發深層次的靈魂拷問?
關注公眾號「松寶寫程式碼」,精選好文,每日面試題
加入我們一起學習,day day up
作者:saucxs | songEagle
來源:原創
一、前言
有這麼一道面試題,如下:
面試題:請詳細介紹一下從輸入 URL 到頁面載入完成的過程 ?
這道題的覆蓋面可以非常廣,很適合作為一道承載知識體系的題目。
每一個前端人員,如果要往更高階發展,必然會將自己的知識體系梳理一遍,沒有牢固的知識體系,無法往更高處走!
你不信這道題承載的知識體系龐大?往下看
二、分析題干
在對於這道題上,如果對於面試官想要知道的是:簡單敘述還是深入敘述 。
所以需要回答到關鍵詞上,不然多而雜,效果不好,抓不住重點。
接下來我們從主幹流程和深入的詳細敘述分別介紹,我覺得這道面試題可能需要 15 分鐘才能講完。
主幹流程回答:是基本功體現,知識歸納能力,面面俱到,點到為止 。
詳細闡述:考察的各個知識點的掌握能力以及掌握到什麼程度 。
三、主幹流程
在將瀏覽器渲染原理、JS 運行機制、JS 引擎解析流程梳理一遍後,感覺就跟打通了任督二脈一樣,有了一個整體的架構,以前的知識點都連貫起來了。
1、從瀏覽器接收 url 到開啟網路請求執行緒(涉及到:瀏覽器機制,執行緒和進程之間的關係等)
2、開啟網路執行緒到發出一個完整的 http 請求(涉及到:dns 查詢,tcp/ip 請求,5 層網路協議棧等)
3、從伺服器接收到請求到對應後台接收到請求(涉及到:均衡負載,安全攔截,後台內部的處理等)
4、後台和前台的 http 交互(涉及到:http 頭,響應碼,報文結構,cookie 等,可以提下靜態資源的 cookie 優化,以及編碼解碼如 gzip 壓縮等)
5、快取問題:http 快取(涉及到:涉及到 http 快取頭部,etag,expired,cache-control 等)
6、瀏覽器接收到 http 數據包後的解析流程(涉及到:html 的詞法分析,然後解析成 dom 樹,同時解析 css 生成 css 規則樹,合併生成 render 樹。然後 layout 布局、painting 渲染、複合圖層的合成、GPU 繪製、外鏈接處理、loaded 和 documentloaded 等)
7、css 可視化格式模型(涉及到:元素渲染規則,如:包含塊,控制框,BFC,IFC 等概念)
8、js 引擎解析過程(涉及到:js 解釋階段,預處理階段,執行階段生成執行上下文,VO(全局對象),作用域鏈,回收機制等)
9、其他(擴展其他模組:跨域,web 安全等)
四、從瀏覽器接收到 url 到開啟網路請求執行緒
涉及到:瀏覽器的進程和執行緒模型,js 的運行機制。
1、瀏覽器是多進程的
(1)瀏覽器是多進程的;
(2)不同類型的標籤頁會開啟一個新的進程;
(3)相同類型的標籤頁會合併到一個進程中。
瀏覽器中各個進程以及作用:
1、瀏覽器進程:只有 1 個進程,(1)負責管理各個標籤的創建和銷毀;(2)負責瀏覽器頁面顯示;(3)負責資源的管理和下載;
2、第三方插件進程:可以是多個進程,負責每一個第三方插件的使用,每一個第三方插件使用時候會創建一個對應的進程;
3、GPU 進程:最多 1 個進程,負責 3D 繪製和硬體加速;
4、瀏覽器渲染進程:可以是多個進程,瀏覽器的內核,每個 tab 頁一個進程,主要負責 HTML、,css,js 等文件的解析,執行和渲染,以及事件處理等。
2、瀏覽器渲染進程(內核進程)
每一個 tab 頁面是瀏覽器內核進程,然後這個每一個進程是多執行緒的,它有幾大類子執行緒:
(1)GUI 執行緒;(2)JS 引擎執行緒;(3)事件觸發執行緒;(4)定時器執行緒;(5)非同步的 http 網路請求執行緒
可以看出來 JS 引擎是內核進程中的一個執行緒,所以常說 JS 引擎時單執行緒的。
3、解析 URL
輸入 url 後,會進行解析(URL 是統一資源定位符)。
URL 包括幾個部分:(1)protocol,協議頭,比如 http,https,ftp 等;(2)host,主機域名或者 IP 地址;(3)port,埠號;(4)path,目錄路徑;(5)query,查詢的參數;(6)fragment,#後邊的 hash 值,用來定位某一個位置。
4、網路請求時單獨的執行緒
每一次網路請求都是需要單獨開闢單獨的執行緒進行,比如 URL 解析到 http 協議,就會新建一個網路執行緒去處理資源下載。
因此瀏覽器會根據解析出得協議,開闢一個網路執行緒,前往請求資源。
五、開啟網路執行緒到發出一個完整的 http 請求
包括:DNS 查詢,tcp/ip 請求構建,五層互聯網協議等等。
1、DNS 查詢得到 IP
如果輸入的域名,需要 DNS 解析成 IP,流程如下:
(1)瀏覽器有快取,直接用瀏覽器快取,沒有就去本機快取,沒有就看是不是 host。
(2)如果還沒有,就向 DNS 域名伺服器查詢(這個過程經過路由,路由也有快取),查詢到對應的 IP。
注意:1、域名查詢的時候有可能經過 CDN 調度器(如果 CDN 有存儲功能);
2、DNS 解析是很耗時的,因此如果解析域名過多,首屏載入會變慢,可以考慮使用 dns-prefetch 優化。
2、tcp/ip 請求構建
http 的本質就是 tcp/ip 請求構建。需要 3 次握手規則簡歷連接,以及斷開連接時候的 4 次揮手。
tcp 將 http 長報文劃分為短報文,通過 3 次握手與服務端建立連接,進行可靠的傳輸。
3 次握手步驟:
客戶端:hello,你是 server 么?
服務端:hello,我是 server,你是 client 么
客戶端:yes,我是 client
建立成功之後,接下來就是正式傳輸數據。
然後,等到斷開連接時,需要進行 4 次揮手(因為是全雙工的,所以需要 4 次握手)。
4 次揮手步驟:
主動方:我已經關閉了向你那邊的主動通道了,只能被動接收了
被動方:收到通道關閉的資訊
被動方:那我也告訴你,我這邊向你的主動通道也關閉了
主動方:最後收到數據,之後雙方無法通訊
tcp/ip 的並發限制
瀏覽器對同一域名下並發的 tcp 連接是有限制的(2-10 個不等)。而且在 http1.0 中往往一個資源下載就需要對應一個 tcp/ip 請求。所以針對這個瓶頸,又出現了很多的資源優化方案。
get 和 post 區別
get 和 post 本質都是 tcp/ip,但是除了 http 外層外,在 tcp/ip 層面也有區別。get 會產生 1 個 tcp 數據包,post 產生 2 個 tcp 數據包。
具體就是:
(1)get 請求時,瀏覽器會把 header 和 data 一起發送出去,伺服器響應 200(返回數據)。
(2)post 請求時,瀏覽器首先發送 headers,伺服器響應 100 continue,瀏覽器再發送 data,伺服器響應 200(返回數據)。
3、五層網路協議棧
客戶端發出 http 請求到伺服器接收,中間會經過一系列的流程。
客戶端發送請求具體:從應用層發動 http 請求,到傳輸層通過三次握手簡歷 tcp/ip 連接,再到網路層的 ip 定址,再到數據鏈路層的封裝成幀,最後在物理層通過物理介質傳輸。
服務端接收請求具體:反過來。
五層網路協議:
1、應用層(DNS,HTTP):DNS 解析成 IP 並發送 http 請求;
2、傳輸層(TCP,UDP):建立 TCP 連接(3 次握手);
3、網路層(IP,ARP):IP 定址;
4、數據鏈路層(PPP):封裝成幀;
5、物理層(利用物理介質傳輸比特流):物理傳輸(通過雙絞線,電磁波等各種介質)。
其實也有一個完整的 OSI 七層框架,與之相比,多了會話層、表示層。
OSI 七層框架:物理層、數據鏈路層、網路層、傳輸層、會話層、表示層、應用層
表示層:主要處理兩個通訊系統中交互資訊的表示方式,包括數據格式交換,數據加密和解密,數據壓縮和終端類型轉換等。
會話層:具體管理不同用戶和進程之間的對話,如控制登錄和註銷過程。
六、從伺服器接收請求到對應後台接收到請求
服務端接收到請求時,內部會有很多處理,其中最主要的是負載均衡和後台處理。
1、負載均衡
對於大型項目,並發訪問很大,一台伺服器吃不消,一般會有若干台伺服器組成一個集群,然後配合反向代理實現均衡負載。均衡負載不止一種實現方式。
概括的說:用戶發送的請求指向調度伺服器(反向代理伺服器,比如 nginx 的均衡負載),然後調度伺服器根據實際的調度演算法,分配不同的請求給對應的集群中的伺服器執行,然後調度伺服器等待實際伺服器的 HTTP 響應,並且回饋給用戶。
2、後台處理
一般後台都部署到容器中。過程如下:
(1)先是容器接收到請求(比如 tomcat 容器);
(2)然後對應容器中的後台程式接收到請求(比如 java 程式);
(3)然後就是後台自己的統一處理,處理完畢後響應結果。
具體概括一下:
(1)一般有的後端有統一的驗證,比如安全攔截,跨域驗證;
(2)如果不符合驗證規則,就直接返回相應的 http 報文(拒絕請求等);
(3)如果驗證通過了,才會進入到實際的後台程式碼,此時程式接收到請求,然後執行查詢資料庫,大量計算等等;
(4)等程式執行完畢後,會返回一個 http 響應包(一般這一步會經過多層封裝);
(5)然後將這個數據包從後端返回到前端,完成交互。
七、後台和前台的 http 交互
前後端的交互,http 報文作為資訊的載體。
1、http 報文結構
報文一般包括:通用頭部,請求/響應頭部,請求/響應體
1.1 通用頭部
Request Url: 請求的 web 伺服器地址
Request Method: 請求方式
(Get、POST、OPTIONS、PUT、HEAD、DELETE、CONNECT、TRACE)
Status Code: 請求的返回狀態碼,如 200 代表成功
Remote Address: 請求的遠程伺服器地址(會轉為 IP)
比如跨區拒絕時,methord 為 option,狀態碼 404/405。
其中 method 分為兩批次:
HTTP1.0 定義了三種請求方法: GET, POST 和 HEAD 方法。
以及幾種 Additional Request Methods:PUT、DELETE、LINK、UNLINK
HTTP1.1 定義了八種請求方法:GET、POST、HEAD、OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。
比如有些狀態碼來判斷:
200——表明該請求被成功地完成,所請求的資源發送回客戶端
304——自從上次請求後,請求的網頁未修改過,請客戶端使用本地快取
400——客戶端請求有錯(譬如可以是安全模組攔截)
401——請求未經授權
403——禁止訪問(譬如可以是未登錄時禁止)
404——資源未找到
500——伺服器內部錯誤
503——服務不可用
大致範圍
1xx——指示資訊,表示請求已接收,繼續處理
2xx——成功,表示請求已被成功接收、理解、接受
3xx——重定向,要完成請求必須進行更進一步的操作
4xx——客戶端錯誤,請求有語法錯誤或請求無法實現
5xx——伺服器端錯誤,伺服器未能實現合法的請求
1.2 請求頭/響應頭
常用的請求頭(部分)
Accept: 接收類型,表示瀏覽器支援的MIME類型
(對標服務端返回的Content-Type)
Accept-Encoding:瀏覽器支援的壓縮類型,如gzip等,超出類型不能接收
Content-Type:客戶端發送出去實體內容的類型
Cache-Control: 指定請求和響應遵循的快取機制,如no-cache
If-Modified-Since:對應服務端的Last-Modified,用來匹配看文件是否變動,只能精確到1s之內,http1.0中
Expires:快取控制,在這個時間內不會請求,直接使用快取,http1.0,而且是服務端時間
Max-age:代表資源在本地快取多少秒,有效時間內不會請求,而是使用快取,http1.1中
If-None-Match:對應服務端的ETag,用來匹配文件內容是否改變(非常精確),http1.1中
Cookie: 有cookie並且同域訪問時會自動帶上
Connection: 當瀏覽器與伺服器通訊時對於長連接如何進行處理,如keep-alive
Host:請求的伺服器URL
Origin:最初的請求是從哪裡發起的(只會精確到埠),Origin比Referer更尊重隱私
Referer:該頁面的來源URL(適用於所有類型的請求,會精確到詳細頁面地址,csrf攔截常用到這個欄位)
User-Agent:用戶客戶端的一些必要資訊,如UA頭部等
常用的響應頭(部分)
Access-Control-Allow-Headers: 伺服器端允許的請求Headers
Access-Control-Allow-Methods: 伺服器端允許的請求方法
Access-Control-Allow-Origin: 伺服器端允許的請求Origin頭部(譬如為*)
Content-Type:服務端返回的實體內容的類型
Date:數據從伺服器發送的時間
Cache-Control:告訴瀏覽器或其他客戶,什麼環境可以安全的快取文檔
Last-Modified:請求資源的最後修改時間
Expires:應該在什麼時候認為文檔已經過期,從而不再快取它
Max-age:客戶端的本地資源應該快取多少秒,開啟了Cache-Control後有效
ETag:請求變數的實體標籤的當前值
Set-Cookie:設置和頁面關聯的cookie,伺服器通過這個頭部把cookie傳給客戶端
Keep-Alive:如果客戶端有keep-alive,服務端也會有響應(如timeout=38)
Server:伺服器的一些相關資訊
一般來說,請求頭部和響應頭部是匹配分析的。
比如:
(1)請求頭部的 Accept 要和響應頭部的 Content-Type 匹配,否則會報錯;
(2)跨域請求中,請求頭部的 Origin 要匹配響應頭的 Access-Control-Allow-Origin,否則會報跨域錯誤;
(3)使用快取,請求頭部的 if-modified-since,if-none-match 分別和響應頭的 Last-modified,etag 對應。
1.3 請求/響應實體
http 請求時,除了頭部,還有消息實體。
請求實體中會將一些需要的參數都放入進入(用於 post 請求)。
比如:(1)實體中可以放參數的序列化形式(a=1&b=2 這種),或者直接放表單(Form Data 對象,上傳時可以夾雜其他以及文件)等等。
響應實體中,就是服務端需要傳給客戶端的內容。
一般現在的介面請求時,實體中就是對應資訊的 json 格式,而像頁面請求這種,裡面就是直接放一個 html 的字元串,然後瀏覽器自己解析並渲染。
1.4 CRLF
CRLF(Carriage-Return Line-Feed),意思是回車換行,一般作為分隔符存在。
請求頭和實體消息之間有一個 CRLF 分隔,響應頭部和響應實體之間用一個 CRLF 分隔。
下圖是對某請求的 http 報文結構的簡要分析:
2、 cookie 以及優化
cookie 是瀏覽器的一種本地存儲方式,一般用來幫助客戶端和服務端通訊的,常用來進行身份校驗,結合服務端的 session 使用。
在登陸頁面,用戶登陸了
此時,服務端會生成一個session,session中有對於用戶的資訊(如用戶名、密碼等)
然後會有一個sessionid(相當於是服務端的這個session對應的key)
然後服務端在登錄頁面中寫入cookie,值就是:jsessionid=xxx
然後瀏覽器本地就有這個cookie了,以後訪問同域名下的頁面時,自動帶上cookie,自動檢驗,在有效時間內無需二次登陸。
一般來說,cookie 是不允許存放敏感資訊的(千萬不要明文存儲用戶名、密碼),因為非常不安全,如果一定要強行存儲,首先,一定要在 cookie 中設置 httponly(這樣就無法通過 js 操作了),另外可以考慮 rsa 等非對稱加密(因為實際上,瀏覽器本地也是容易被攻克的,並不安全)
比如這樣的場景:
客戶端在域名A下有cookie(這個可以是登陸時由服務端寫入的)
然後在域名A下有一個頁面,頁面中有很多依賴的靜態資源(都是域名A的,譬如有20個靜態資源)
此時就有一個問題,頁面載入,請求這些靜態資源時,瀏覽器會默認帶上cookie
也就是說,這20個靜態資源的http請求,每一個都得帶上cookie,而實際上靜態資源並不需要cookie驗證
此時就造成了較為嚴重的浪費,而且也降低了訪問速度(因為內容更多了)
當然了,針對這種場景,是有優化方案的(多域名拆分)。具體做法就是:
(1)將靜態資源分組,分別放到不同的域名下(如 static.base.com)
(2)而 page.base.com(頁面所在域名)下請求時,是不會帶上 static.base.com 域名的 cookie 的,所以就避免了浪費
說到多域名拆分,還有一個問題?
(1)在移動端,如果請求的域名數過多,會降低請求速度(因為域名整套解析流程很浪費時間,而且移動端一般頻寬比不上 PC)。
(2)這時候有個優化方案:dns-prefetch(這個是幹嘛的?就是讓瀏覽器空閑時提前解析 dns 域名,不過請合理使用)
關於 cookie 的交互,可以看下圖總結
3、gzip 壓縮
首先,gzip 是請求頭裡的 Accept-Encoding:瀏覽器支援的壓縮類型之一。gzip 是一種壓縮格式,需要瀏覽器支援才有效(一般瀏覽器都支援),而且 gzip 的壓縮率很好(高達 70%);
然後 gzip 一般是 apach,nginx,tomcat 等 web 伺服器開啟。
除了 gzip 的壓縮格式,還有 deflate,沒有 gzip 高效,不流行。
所以一般只需要在伺服器上開啟 gzip 壓縮,然後之後的請求都是基於 gzip 壓縮格式的,非常方便。
4、長連接和短連接
首先我們看一下 tcp/ip 的定義:
(1)長連接:一個 tcp/ip 連接上可以連續發送多個數據包,tcp 連接保持期間,如果乜有數據包發送,需要雙方發檢測包以維持此連接,一般需要自己做在線維持(類似於心跳包)。
(2)短連接:通訊雙方有數據交互是,簡歷一個 tcp 連接,數據發送完成後,則斷開此 tcp 連接。
我們再看一下 http 層面上:
(1)http1.0 中,默認是使用的短連接,瀏覽器每進行一次 http 操作,就建立一次連接,任務結束就中斷連接,比如每一個靜態資源請求都是一個單獨的連接
(2)http1.1 開始,默認是使用長連接,長連接會設置 connection: keep-alive,在長連接的情況下,當一個網頁打開後,客戶端和服務端之間用於傳輸 http 的 tcp 連接不會關閉,如果客戶端再次訪問伺服器這個頁面,會繼續使用這一條已經建立起來的連接。
注意:kee-alive 不會永遠保持,他有一個持續時間,一般服務中進行配置,另外長連接是需要客戶端和伺服器端都支援才有效。
5、http2.0
http2.0 不是 https,它相當於 http 的下一代規範(https 也可能是 http2.0 規範)
比較一下 http1.1 和 http2.0 顯著不同地方:
(1)http1.1 中,每請求一個資源,都是需要開啟一個 tcp/ip 連接的,所以對應的結果是:每一個資源對應一個 tcp/ip 請求,由於 tcp/ip 本身有個並發數的限制,資源一旦多了,速度會下降慢下來。
(2)http2.0 中,一個 tcp/ip 請求可以請求多個資源,也就說,只要一次 tcp/ip 請求,就可以請求多個資源,分隔成更小的幀請求,速度明顯提升。
所以,如果 http2.0 全面應用的,很多 http1.1 中的優化方案無需用到(比如:精靈圖,靜態組員多域名拆分等)。
現在介紹一下 http2.0 的一些特性:
(1)多路復用(一個 tcp/ip 可以請求多個資源);
(2)首部壓縮(http 頭部壓縮,減少體積);
(3)二進位分幀(在應用層跟傳輸層之間增加一個二進位分幀層,改進傳輸性能,實現低延遲和高吞吐);
(4)伺服器端推送(服務端可以對客戶端的一個請求發出多個響應可以主動通知客戶端);
(5)請求優先順序(如果流被賦予了優先順序,就會基於這個優先順序來處理,有伺服器決定需要多少資源來處理該請求)
6、https
https 就是安全版本的 http,比如一些支付操作服務基本上都是基於 https 的,因為 http 請求的安全係數太低了。
簡單來看,https 和 http 區別是:在請求前,會建立 ssl 鏈接,確保接下來的通訊都是加密的,無法輕易截取分析。
一般來說,需要將網站升級到 https,需要後端支援(後端需要申請證書等),然後 https 的開銷比 http 大(因為要額外的簡歷安全鏈接和加密等),所以一般來說 http2.0 配合 https 的體驗更佳(http2.0 更快)。
主要關注的就是 SSL/TLS 的握手流程,如下(簡述):
(1)瀏覽器請求建立 SSL 鏈接,並向服務端發送一個隨機數(client random)和客戶端支援的加密方法,比如是 RSA 加密,此時是明文傳輸。
(2)服務端從中選出一組加密演算法和 hash 演算法,回復一個隨機數(server random),並將自己的身份資訊以證書的形式發回給瀏覽器(證書中包含了網站地址,非對稱加密的公鑰,以及證書頒發機構等資訊)。
(3)瀏覽器收到服務端證書後:
1、首先驗證證書的合法性(頒發機構是否合法,證書包含的網站是否和正在訪問的一樣),如果證書信任,瀏覽器會顯示一個小頭鎖,否則會有提示。
2、用戶接受到證書後(不管信任不信任),瀏覽器會產生一個新的隨機數(Premaster secret),然後證書中的公鑰以及制定的加密方法加密Premaster secret
(預主密碼),發送給伺服器。
3、利用 client random,server random 和 premaster secret 通過一定的演算法生成 HTTP 鏈接數據傳輸的對稱加密 key-『sessionkey』
4、使用約定好的 hash 演算法計算握手消息,並使用生成的 session key 對消息進行加密,最後將之前生成的所有資訊發送給服務端。
(4)服務端收到瀏覽器的回復
1、利用已知的加密方式與自己的私鑰進行解密,獲取 Premaster secret,
2、和瀏覽器相同規則生成 session key,
3、使用 session key 解密瀏覽器發來的握手消息,並驗證 hash 是否與瀏覽器發來的一致,
4、使用 session key 加密一段握手消息,發送給瀏覽器
(5)瀏覽器解密並計算握手消息的 hash 值,如果與服務端發來的 hash 一致,此時握手結束。
之後所有的 https 通訊數據將由之前瀏覽器生成的 session key 並利用對稱加密演算法進行加密
八、快取問題:http 快取
http 交互中,快取很大程度上提升效率。
1、強快取與弱快取
快取可以簡單劃分為兩種類型:強快取(200 from cache)與協商快取(304);
區別簡介一下:
(1)強快取(200 from cache)時,瀏覽器如果判斷本地快取未過期,就直接使用,無需發起http請求。
(2)協商快取(304)時,瀏覽器會向伺服器發起http請求,然後服務端告訴瀏覽器文件未改變,讓瀏覽器使用戶本地快取。
對於協商快取,可以使用 ctrl/command + F5 強制刷新,使得協商快取無效。
對於強制快取,在未過期,必須更新資源路徑才能發送新的請求。
2、快取頭部簡述
怎麼在程式碼中區分強快取和協商快取?
通過不同的 http 的頭部控制。
屬於強制快取的:
(http1.1)Cache-Control/Max-Age
(http1.0)Pragma/Expires
注意:cache_control 的值:public,private,no-store,no-cache,max-age
屬於協商快取的:
(http1.1)If-None-Match/E-tag
(http1.0)If-Modified-Since/Last-Modified
再提一點,其實 HTML 頁面中也有一個 meta 標籤可以控制快取方案-Pragma
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
不過,這種方案還是比較少用到,因為支援情況不佳,譬如快取代理伺服器肯定不支援,所以不推薦。
3、快取頭部區別
在 http1.1 中,出現了一些新內容,彌補 http1.0 不足。
http1.0 中的快取控制:
(1)Pragma:嚴格來說不算快取控制的頭部,設置了 no-cache 會讓本地快取失效(屬於編譯控制,來實現特定的指令)。
(2)Expires:服務端配置,屬於強快取,用來控制在規定的時間之前,瀏覽器不會發送大量請求,而直接使用本地快取,注意:Expires 一般對應伺服器端時間,比如:Expires:Fri, 30 Oct 1998 14:19:41
(3)If-Modified-Since/Last-modified:這兩個是成對出現的,屬於協商快取。其中瀏覽器頭部是 If-Modified-Since,而服務端是 Last-Modified,發送請求時,如果這兩個匹配成功,代表伺服器資源並沒有改變,服務端不會返回資源實體,而是返回頭部,告知瀏覽器使用本地快取。Last-modifed 指文件最後的修改時間,只能精確到 1S 以內。
http1.1 中快取的控制:
(1)cache-control :快取的控制頭部,有 nocache,max-age 等多個取值。
(2)Max-Age:服務端配置的,用來控制強快取的,在規定的時間內,瀏覽器不用發出請求,直接使用本地的快取。Max-Age 是 cache-control 的值,比如:cache-control: max-age=60*1000,值是絕對時間,瀏覽器自己計算。
(3)If-None-Match/E-tag:這兩個是成對的出現的,屬於協商快取,其中瀏覽器頭部是 If-None-Match,而服務端是 E-tag,同樣,發出請求後,如果 If-None-Match 和 E-tag 匹配,代表內容沒有變化,告訴瀏覽器使用本地快取,和 Last-modified 不同,E-tag 更精確,它類似於指紋一樣,基於 FileEtag INode Mtime Size 生成的,就是說文件變,指紋就會變,沒有精確度的限制。
Cache-Control 相比 Expires?
1、都是強制快取。
2、Expires 使用服務端時間,因為存在時區,和瀏覽器本地時間可以修改問題,在 http1.1 不推薦使用 Expires;Cache-Control 的 Max-Age 是瀏覽器端本地的絕對時間。
3、同時使用 Cache-Control 和 Expires,Cache_control 優先順序高。
E-tag 相比 Last-Modified?
1、都是協商快取。
2、Last-modified 指的是服務端文件最後改變時間,缺陷是精確只能到 1s,文件周期性的改變,導致快取失效;E-tag 是一種指紋機制,文件指紋,只要文件改變,E-tag 立刻變,沒有精度限制。
3、帶有 E-tag 和 Last-modified 時候,E-tag 優先順序高。
各大快取頭部的整體關係如下圖
九、解析頁面流程
前面提到是 http 交互,接下來是瀏覽器獲取到 html,然後解析,渲染。
1、流程簡述
瀏覽器內核拿到內容後,渲染大致分為以下幾步:
(1)解析 html,構建 DOM 樹;同時解析 CSS,生成 CSS 規則樹。
(2)合併 DOM 樹和 CSS 規則樹,生成 Render 樹。
(3)布局 Render 樹(layout/reflow),負責各元素的尺寸,位置計算。
(4)繪製 render 樹(paint),繪製頁面像素資訊。
(5)瀏覽器會將各層的資訊發給 GPU。GPU 會將各層合成(composite),顯示在螢幕上。
如下圖:
2、html 解析,構建 DOM
這一步的流程是這樣的:瀏覽器解析 HTML,構建 DOM 樹。實際上,稍微展開一下。
解析 html 到構建 dom 過程簡述如下:
Bytes -> characters -> tokens -> nodes ->DOM
比如,有這樣一個 html 頁面:
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
</body>
</html>
瀏覽器的處理如下:
列舉一下其中一些重點過程:
1. Conversion 轉換:瀏覽器將獲得的HTML內容(Bytes)基於他的編碼轉換為單個字元
2. Tokenizing 分詞:瀏覽器按照HTML規範標準將這些字元轉換為不同的標記token。每個token都有自己獨特的含義以及規則集
3. Lexing 詞法分析:分詞的結果是得到一堆的token,此時把他們轉換為對象,這些對象分別定義他們的屬性和規則
4. DOM 構建:因為HTML標記定義的就是不同標籤之間的關係,這個關係就像是一個樹形結構一樣
例如:body 對象的父節點就是 HTML 對象,然後段略 p 對象的父節點就是 body 對象
最後的 DOM 樹:
3、css 解析,構建 css 規則樹
CSS 規則樹的生成也是類似
Bytes → characters → tokens → nodes → CSSOM
比如:style.css 內容如下:
body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
最終的 CSSOM 樹就是
4、構建渲染樹
當 DOM 樹和 CSSOM 都有了後,就要開始構建渲染樹了。一般來說,渲染樹和 DOM 樹相對應的,但不是嚴格意義上的一一對應。
因為有一些不可見的 DOM 元素不會插入到渲染樹中,如 head 這種不可見的標籤或者 display: none
等
5、渲染
有了 render 樹,接下來就是開始渲染,基本流程如下:
圖中重要的四個步驟就是:
(1)計算 CSS 樣式;
(2)構建渲染樹;
(3)布局,主要定位坐標和大小,是否換行,各種 position overflow z-index 屬性;
(4)繪製,將影像繪製出來。
然後,圖中的線與箭頭代表通過 js 動態修改了 DOM 或 CSS,導致了重新布局(Layout)或渲染(Repaint)
這裡 Layout 和 Repaint 的概念是有區別的:
(1)Layout,也稱為 Reflow,即迴流。一般意味著元素的內容、結構、位置或尺寸發生了變化,需要重新計算樣式和渲染樹。
(2)Repaint,即重繪。意味著元素髮生的改變只是影響了元素的一些外觀之類的時候(例如,背景色,邊框顏色,文字顏色等),此時只需要應用新樣式繪製這個元素就可以了。
迴流的成本開銷要高於重繪,而且一個節點的迴流往往回導致子節點以及同級節點的迴流, 所以優化方案中一般都包括,盡量避免迴流。
6、什麼引起迴流
1.頁面渲染初始化
2.DOM 結構改變,比如刪除了某個節點
3.render 樹變化,比如減少了 padding
4.窗口 resize
5.最複雜的一種:獲取某些屬性,引發迴流,
很多瀏覽器會對迴流做優化,會等到數量足夠時做一次批處理迴流,
但是除了 render 樹的直接變化,當獲取一些屬性時,瀏覽器為了獲得正確的值也會觸發迴流,這樣使得瀏覽器優化無效,包括
(1) offset(Top/Left/Width/Height)
(2) scroll(Top/Left/Width/Height)
(3) cilent(Top/Left/Width/Height)
(4) width,height
(5) 調用了getComputedStyle()或者IE的currentStyle
迴流一定伴隨著重繪,重繪卻可以單獨出現。
優化方案:
(1)減少逐項更改樣式,做好一次性更改樣式。或者將樣式定義為 class,並一次性更新。
(2)避免循環操作 dom,創建一個 documentFragment 或 div,在他上面進行所有的 dom 操作,最後添加到 window.document 中。
(3)避免多次讀取 offset 等屬性,無法避免就將他們快取到變數中。
(4)將複雜的元素絕對定位或者固定定位,使他們脫離文檔流,否則迴流代價很高。
注意:改變字體大小會引起迴流。
再看一個例子:
var s = document.body.style;
s.padding = "2px"; // 迴流+重繪
s.border = "1px solid red"; // 再一次 迴流+重繪
s.color = "blue"; // 再一次重繪
s.backgroundColor = "#ccc"; // 再一次 重繪
s.fontSize = "14px"; // 再一次 迴流+重繪
// 添加node,再一次 迴流+重繪
document.body.appendChild(document.createTextNode('abc!'));
7、簡單層和複合層
上述中的渲染中止步於繪製,但實際上繪製這一步也沒有這麼簡單,它可以結合複合層和簡單層的概念來講。
簡單介紹下:
(1)可以默認只有一個複合層,所有的 DOM 節點都是在這個複合圖層下。
(2)如果開啟了硬體加速功能,可以將某一個節點變成複合圖層。
(3)複合圖層之間的繪製互不干擾,直接 GPU 直接控制。
(4)簡單圖層中,就算是 absolute 等布局,變化時不影響整體迴流,但是由於在同一個圖層中,仍然會影響繪製的,因此做動畫時候性能仍然很低。而且複合層是獨立的,所以一般做動畫推薦使用硬體加速。
更多參考://segmentfault.com/a/1190000012925872#articleHeader16
8、Chrome 的調試
Chrome 的開發者工具中,Performance 中可以看到詳細的渲染過程:
9、資源外鏈的下載
上面介紹了 html 解析,渲染流程。但實際上,在解析 html 時,會遇到一些資源連接,此時就需要進行單獨處理了。
簡單起見,這裡將遇到的靜態資源分為一下幾大類(未列舉所有):
- 遇到外鏈的處理
- css 樣式資源
- js 腳本資源
- img 圖片類資源
以下針對每種情況進行詳細說明:
- 遇到外鏈的處理
當遇到上述的外鏈時,會單獨開啟一個下載執行緒去下載資源(http1.1 中是每一個資源的下載都要開啟一個 http 請求,對應一個 tcp/ip 鏈接)
- 遇到 css 樣式資源
css 資源處理特點:
(1)css 下載時非同步的,不會阻塞瀏覽器構建 DOM 樹;
(2)但是會阻塞渲染,也就是在構建 render 樹時,等到 css 下載解析後才進行(與瀏覽器優化有關,防止 css 規則不斷變化,避免重複的構建)
(3)有例外,遇到 media query 聲明的 css 是不會阻塞構建 render 樹
- 遇到 js 腳本資源
JS 腳本資源的處理有幾個特點:
(1)阻塞瀏覽器的解析,也就是說發現一個外鏈腳本時,需等待腳本下載完成並執行後才會繼續解析 HTML。
(2)瀏覽器的優化,一般現代瀏覽器有優化,在腳本阻塞時,也會繼續下載其它資源(當然有並發上限),但是雖然腳本可以並行下載,解析過程仍然是阻塞的,也就是說必須這個腳本執行完畢後才會接下來的解析,並行下載只是一種優化而已。
(3)defer 與 async,普通的腳本是會阻塞瀏覽器解析的,但是可以加上 defer 或 async 屬性,這樣腳本就變成非同步了,可以等到解析完畢後再執行。
注意,defer 和 async 是有區別的:defer 是延遲執行,而 async 是非同步執行。
簡單的說:
(1)async 是非同步執行,非同步下載完畢後就會執行,不確保執行順序,一定在 onload 前,但不確定在 DOMContentLoaded 事件的前或後。
(2)defer 是延遲執行,在瀏覽器看起來的效果像是將腳本放在了 body 後面一樣(雖然按規範應該是在 DOMContentLoaded 事件前,但實際上不同瀏覽器的優化效果不一樣,也有可能在它後面)。
- 遇到 img 圖片類資源
遇到圖片等資源時,直接就是非同步下載,不會阻塞解析,下載完畢後直接用圖片替換原有 src 的地方
10、loaded 和 domcontentloaded
對比:
(1)DOMContentLoaded 事件觸發時,僅當 DOM 載入完成,不包括樣式表,圖片(譬如如果有 async 載入的腳本就不一定完成)。
(2)load 事件觸發時,頁面上所有的 DOM,樣式表,腳本,圖片都已經載入完成了。
十、Reference
1、//github.com/saucxs/full_stack_knowledge_list
2、//blog.csdn.net/sinat_21455985/article/details/53508115
3、//book.douban.com/subject/26960678/
4、//github.com/kaola-fed/blog/issues/271
其他
songEagle開發知識體系構建,技術分享,項目實戰,實驗室,帶你一起學習新技術,總結學習過程,讓你進階到高級資深工程師,學習項目管理,思考職業發展,生活感悟,充實中成長起來。問題或建議,請公眾號留言。
關注福利
1、內推福利
回復「校招」獲取內推碼
回復「社招」獲取內推
回復「實習生」獲取內推
後續會有更多福利
2、學習資料福利
回復「演算法」獲取演算法學習資料
3、每日一題
正在更新中