年前,讀完了《高性能站點建設指南》,但是一直沒有整理。年後回來和同事一塊兒出了份前端面試題,涉及到了關於性能優化的問題,在此特梳理一下。css
大量的公司在開發功能業務時,僅僅關注功能點的實現,對於性能方面要求很是低甚至不做爲考慮範圍。在遇到一些性能瓶頸時,也每每經過加機器的暴力方式去減緩,但那並不是解決這個問題的根本。做爲前端project師。大部分人爲了迎合需求一直在學習JavaScript、CSS、HTML5及Node。很是少去關注性能方面的東西。然而,有些性能的優化點僅僅需要花費很是少的時間和精力就能換來巨大的改善用戶體驗。html
在陳述前端性能優化的問題以前,咱們先思考例如如下問題:前端
一個頁面從輸入 URL 到頁面載入顯示完畢。這個過程當中都發生了什麼?git
IP
地址,包括 DNS
詳細的查找過程,包括:瀏覽器緩存、系統緩存、路由器緩存等;web
server發送一個 HTTP
請求;http://example.com
到 http://www.example.com
);HTTP
響應;HTML
;HTML
中的資源(如圖片、音頻、視頻、CSS
、JS
等等);參考地址:http://igoro.com/archive/what-really-happens-when-you-navigate-to-a-url/web
性能黃金法則:僅僅有10%-20%的終於用戶響應時間花在了下載HTML文檔上。其他的80%-90%時間花在了下載頁面中的所有組件上。面試
在闡述優化點以前。有必要的先說明一下HTTP。其做爲瀏覽器和server之間的一種傳輸協議。在整個過程當中的做用相當重要。對HTTP不少其它的瞭解,推薦閱讀《HTTP權威指南》。瀏覽器
壓縮響應是最卓有成效的優化方式,瀏覽器可以使用Accept-Encoding頭來聲明支持壓。server使用Content-Encoding頭確認響應已被壓縮。緩存
==Request Headers== Accept-Encoding:gzip ==Response Headers== Content-Encoding:gzip
假設瀏覽器在其緩存中保留了組件的一個副本。但並不肯定它是否仍然有效。就會生成一個條件Get請求。假設確認緩存在副本仍然有效,瀏覽器就可以使用緩存中的副本。ruby
典型狀況下,緩存副本的有效性源自其最後改動時間。基於響應中的Last-Modified頭,瀏覽器可以知道組件最後的改動時間。性能優化
它會使用If-Modified-Since頭將最後改動時間發送給server。
假設組件沒有被改動過。server會返回一個「304 Not Modified」狀態碼並再也不發送響應體。從而獲得一個更小且更快的相應。
在HTTP1.1中可以使用ETag和If-None-Match進行條件GET請求(如下講述)。
==Request Headers== If-Modified-Since:Thu, 07 Apr 2016 08:30:15 GMT ==Response Headers== Last-Modified:Thu, 07 Apr 2016 08:30:15 GMT
條件GET請求和304響應有助於讓頁面載入得更快,但仍需要在client和server之間進行一次往返確認。以運行有效性檢查。Expires頭明白指出瀏覽器可否夠使用組件的緩存副本。假設組件沒有過時,瀏覽器就會使用緩存版本號而不會進行不論什麼HTTP請求。
==Response Headers== Expires:Thu, 07 Apr 2019 08:30:15 GMT
HTTP構建在傳輸控制協議TCP(Transmission Control Protocol)之上。HTTP早期實現中。每個HTTP請求都要打開一個socket鏈接。
持久鏈接可以確保在單獨的鏈接上進行多個請求。瀏覽器和server使用Connection頭來指明對Keep-Alive的支持。
在HTTP1.1中並不是必須的,HTTP1.1中定義的管道可以在一個單獨的socket上發送多個請求,管道性能優於持久鏈接。
但IE7不支持,因此很是多瀏覽器和server仍然包括Keep-Alive。
==Request Headers== Connection:keep-alive ==Response Headers== Connection:keep-alive
性能黃金法則中提到80%~90%時間花在HTML文檔中組件下載。所以。下降組件的數量,並由此下降HTTP請求的數量。
是改善響應時間的最簡單途徑。
對於「圖片超連接」的狀況,可以使用圖片地圖下降頁面圖片個數,從而下降HTTP請求。
其分爲server端圖片地圖和client圖片地圖。詳見:HTML5-嵌入內容
同圖片地圖,CSS Sprites也可合併圖片。將多個圖片合併到一個單獨的圖片中,使用CSS的background-position屬性將HTML元素放到背景圖片中指望的位置上。
<div style="background-image: url('sprites.git'); background-position: -260px -90px; width: 26px; height: 26px;">
</div>
注意:圖片地圖中的圖片必須是連續的。而CSS Sprites則沒有這個限制。
經過使用data: [<mediatype>][;base64],<data>
模式可以在Web頁面中包括圖片。而無需額外的HTTP請求(IE不支持)。要注意,在跨頁面時不會被緩存。
不要去內聯公司的logo,因爲編碼過的logo會致使頁面變大。
聰明的作法是:使用CSS將內聯圖片做爲背景。將其放在外部樣式表中,數據可以緩存在樣式表內部。儘管將內聯圖片放置在外部樣式表中添加了一個額外的HTTP請求(請求樣式表),但被緩存後可以獲得額外的收穫。
固然,對於僅僅使用一次(如,驗證碼)直接可以寫在頁面上。
演示樣例:存放到樣式表
.cart {background-img: url(data:image/png;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDA...)}
演示樣例:直接頁面嵌入
<img src="data:image/png;base64,/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDA..." />
合併腳本和樣式表,是最普通只是的性能優化方式。可以使用Grunt、Webpack、Gulp等工具。這裏再也不贅述。需要思考的是:一個多頁面的站點會有大量的模塊,而模塊的組合狀況複雜,怎樣合併模塊值得花時間去分析一下本身的頁面。確保組合的數量是可管理的。
內容公佈網路(CDN)是一組分佈在多個不一樣地理位置的Webserver,用於更加有效地向用戶公佈內容,其目的是使用戶可就近取得所需內容,解決 Internet網絡擁擠的情況。提升用戶訪問站點的響應速度。使用CDN。需要注意:更新內容後,CDN的生效時間!
Expires頭在前面已經闡述過,其目的主要是最大化利用瀏覽器的緩存來改善頁面的性能。
瀏覽器(和代理)使用緩存來下降HTTP請求的數量,並下降HTTP響應的大小。
==Response Headers== Expires:Thu, 07 Apr 2019 08:30:15 GMT
其告訴瀏覽器可以使用該組件的緩存。直到2019年4月7日上午8時30分15秒。
HTTP1.1中引入了Cache-Control
來克服Expires
頭的限制。
Expires
頭使用一個特定的時間。它要求server和client的時間嚴格同步(固然,可以經過Apache mode_expires模塊中的ExpiresDefault以相對方式設置日期);另外,過時日期需要常常檢查。一旦到達過去日期還需要在server端配置中提供一個新的日期。
Cache-Control: max-age=秒數
指定組件緩存多久(下述,緩存1年)。
==Response Headers== Cache-Control: max-age=31536000
max-age
可以消除Expires的限制,但對於不支持HTTP1.1的瀏覽器。可以同一時候設置兩者。兩者同一時候存在時,HTTP規定max-age指令將重寫Expires頭。
假設咱們將組件配置可以在瀏覽器端進行緩存,當這些組件改變時用戶怎樣得到更新呢?設置了Expires頭時。過時前會一直使用緩存版本號(從硬盤上讀取組件),瀏覽器不會更新。爲了確保用戶可以獲取組件的最新版本號,需要在所有HTML頁面改動組件的文件名稱。常用方式是添加MD5戳。
實體標籤(Entity Tag。ETag)是Webserver和瀏覽器用於確認緩存組件有效性的一種機制。
瀏覽器下載組件後,會進行緩存。再次使用該組件時,會根據Expires頭的值,推斷是否發起請求。假設過時了。瀏覽器在重用以前必須檢查他是否仍然有效,發送條件GET請求(前面已經說起)。假設是有效的。server會返回「304 Not Modified」,不會返回整個組件;這比簡單地下載所有過時的組件效率要高。
server在檢測緩存組件是否和原始server上的組件匹配時有兩種方式:
原始server經過Last-Modified響應頭來返回組件最新改動日期。
==Request Headers== GET /1/4/A/1_ligang2585116.jpg Host: avatar.csdn.net
==Response Headers== HTTP 1.1 200 OK Last-Modified: Wed, 11 Nov 2015 20:24:15 GMT Content-Length: 19613
下一次請求http://avatar.csdn.net/1/4/A/1_ligang2585116.jpg時,瀏覽器會使用If-Modified-Since頭將最新改動日期傳回到原始server以進行比較。假設server上組件的最新改動日期與瀏覽器傳回的值匹配,返回304,不會傳送19613字節的數據。
==Request Headers== GET /1/4/A/1_ligang2585116.jpg Host: avatar.csdn.net
If-Modified-Since: Wed, 11 Nov 2015 20:24:15 GMT
==Response Headers== HTTP 1.1 304 Not Modified
ETag在HTTP1.1中引入,ETag是惟一標識了一個組件的一個特定版本號的字符串。
==Request Headers== GET /1/4/A/1_ligang2585116.jpg Host: avatar.csdn.net
==Response Headers== HTTP 1.1 200 OK Last-Modified: Wed, 11 Nov 2015 20:24:15 GMT ETag: "8224274EB79860E83F60346E0EEBE99A" Content-Length: 19613
ETag的加入爲驗證明體提供了比較新改動日期更靈活的機制。
好比,假設實體根據User-Agent或Accept-Language頭而改變。實體的狀態可以反映在ETag中。
瀏覽器會使用If-None-Match頭將ETag傳回原始server以進行比較。假設server上組件的ETag值與瀏覽器傳回的值匹配,返回304,不會傳送19613字節的數據。
==Request Headers== GET /1/4/A/1_ligang2585116.jpg Host: avatar.csdn.net
If-Modified-Since: Wed, 11 Nov 2015 20:24:15 GMT
If-None-Match: "8224274EB79860E83F60346E0EEBE99A"
==Response Headers== HTTP 1.1 304 Not Modified
ETag一般使用組件的某些屬性構造而成,這些屬性相應特定的、寄宿了站點server來講是惟一的。當瀏覽器從一臺server上獲取了原始組件。以後。又向另一臺不一樣的server發送提交GET請求,ETag是不會匹配的–這對於server集羣來處理請求的站點非常常見,大大下降有效驗證的成功率。
If-None-Match比If-Modified-Since具備更高的優先級。你可能但願假設ETag不匹配但最新改動時間一樣,也能發送一個「304 Not Modified」響應,但實際並不是這種。
HTTP1.1規範https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.4,假設請求中同一時候出現了這兩個頭,則原始server「禁止(MUST NOT)」返回304。除非請求中的條件頭字段所有一致。
上面描寫敘述了ETag對於集羣式站點的嚴重問題。但你可能會說,利用長久Expires頭。使組件更長時間緩存到client。但是一旦用戶點擊了Reload或Refreshbutton。依舊會產生條件GET請求。
因此,你可以定義ETag僅僅保留大小和時間戳做爲內容,或者直接移除ETag。