為什麼要提升web性能?
Web性能黃金準則:隻有10%~20%的最終使用者響應時間花在了下載下傳html文檔上,其餘的80%~90%時間花在了下載下傳頁面元件上。
web性能對于使用者體驗有及其重要的影響,根據著名的`2-5-8`原則:
當使用者在2秒以内得到響應,會感覺系統的響應非常快
當使用者在2-5秒之内得到響應,會感覺系統的響應速度還可以
當使用者在5-8秒之内得到響應,會感覺系統的響應非常慢,但還可以接受
當使用者在8秒之後都沒有得到響應,會感覺系統糟透了,甚至系統已經挂掉;要麼打開競争對手的網站,要麼重新發起第二次請求
凡事都需要研究,通過科學的研究我們就可以找到事物的發展規律。這裡要感謝雅虎的工程師總結的14條前端優化法則,使得我們可以站在巨人的肩膀上。《高性能網站建設》這本書中的14條優化原則,總結起來主要是以下個方面的優化:
減少HTTP請求
頁面内部優化
啟用緩存
減少下載下傳量
網絡連接配接上的優化
為什麼減少HTTP請求可以提高Web性能?
要回答這個問題,我們就要了解當浏覽器向伺服器發送一個http請求知道擷取資料都經曆哪些過程:
開啟一個連結(tcp/ip的三次握手過程) -》 發送請求 -》 等待(網絡延遲跟伺服器的處理時間)-》 下載下傳資料
我們看一下百度首頁中的http請求在各階段耗費的時間,上面不同的顔色代表下圖中的不同階段

可以看到除了圖檔之外,其餘大部分http請求的事件花在了建立連接配接與等待階段。
http協定建立在TIC/IP協定之上,在TCP/IP協定中,TCP協定提供可靠的連接配接服務,采用三次握手建立一個連接配接。 簡單來說三次握手就是一個身份确認的過程:
(第一次握手:主機A發送位碼為syn=1,随機産生seq number=1234567的資料包到伺服器,主機B由SYN=1知道,A要求建立聯機;)
晴兒:你是潇哥哥嗎,我是晴兒
(第二次握手:主機B收到請求後要确認聯機資訊,向A發送ack number=(主機A的seq+1),syn=1,ack=1,随機産生seq=7654321的包)
潇劍:這貨是誰,一箫一劍走江湖,下一句是什麼?
(第三次握手:主機A收到後檢查ack number是否正确,即第一次發送的seq number+1,以及位碼ack是否為1,若正确,主機A會再發送ack number=(主機B的seq+1),ack=1,主機B收到後确認seq值與ack=1則連接配接建立成功。)
晴兒:這首詩。。。你真的是潇哥哥,一蕭一劍走江湖,千古情愁酒一回。。。
潇劍:晴兒,你真的是晴兒。。。。
(啪啪啪啪啪啪啪啪啪啪啪啪啪啪啪。。。。。。。。。。。。)
言歸正傳,這個過程也是需要消耗時間的,在百度首頁找到一個極端的例子:
而等待的時間通常也大于内容下載下傳的時間,這裡同樣找到一個極端例子:
由此我們可以得出結論:一個http請求絕大多數的時間消耗在了建立連接配接跟等待的時間,優化的方法是減少http請求。
如何提高web性能?
1、減少HTTP請求
一般來說要減少http請求通常從兩個方面下手:減少圖檔的請求、減少腳本檔案與樣式表的請求
圖檔的減少通常有兩種方式:css sprites、内聯圖檔、IconFont。
CSS Sprites:将多張圖檔合并成一幅單獨的圖檔,使用css的background-position屬性,将html元素的背景圖檔放到sprites 圖檔中的期望位置上。使用這項技術的附加優點是他降低了下載下傳量,合并後的圖檔比分離的圖檔和更小,因為它降低了圖檔自身的開銷(顔色表、格式資訊等等)。實際項目中css sprites是一項體力活,因為開發過程中需要對這張大圖進行維護(添加、減少圖檔),張鑫旭同學的文章中有介紹如何管理sprites圖檔可以作為參考(這裡)。如果需要在頁面中為背景、連結、導航欄提供大量的圖檔,css sprites絕對是一種優秀的解決方案(幹淨的标簽、較少的圖檔、較短的響應時間)。
内聯圖檔:通過使用data:URL模式可以再頁面中包含圖檔而無需任何額外的請求。缺點就是IE8以下的浏覽器不支援這種方式,而IE8在資料大小上有限制,隻能支援23kb以内的資料。對于較小的圖檔來說可以直接内聯到web頁面中,但對于大圖檔内聯到頁面裡會導緻頁面變大,聰明的做法是使用css,将内聯的圖檔作為背景使用,并放到外部樣式表中,這意味着資料可以緩存在樣式表内部。使用外部樣式表雖然增加了一個http請求,但樣式可以被浏覽器緩存,得到額外的收獲。另外一點需要注意:base64是有損壓縮。
IconFont:圖示字型,這是近年來新流行的一種以字型代替圖檔的技術。它可以适應任何分辨率而不會出現圖檔模糊問題,與圖檔相比它具有更小的容量,更高的靈活性(像字型一樣可以設定圖示大小、顔色、透明度、hover狀态、反轉等),IE8以上的浏覽器都支援該技術。在使用IconFont之前,你首先要确定你選則的字型庫是否是收費。詳細内容可以參考這篇文章:圖示字型化淺談
減少腳本與樣式表的請求主要原則就是合并。在實際開發中我們遵循子產品化的原則将代碼分散到許多小檔案中,按照軟體開發的原則這是完全正确的,但對于上線頁面來說,每一個檔案都會産生一個http請求,嚴重影響性能。和css sprites一樣,将這些小檔案合并到一個檔案中,可以減少http請求的數量并縮短最終使用者響應時間。在合并過程中我們還需要使用工具精簡(移除不必要的字元以減小檔案大小縮減下載下傳時間)和混淆(除了移除不必要字元外,還會改寫源代碼,比如函數和變量名使用更短的标量名)Javascript代碼。對于采用AMD或CMD進行子產品化開發的同學,在合并過程中通常會将依賴的其他子產品打包到一個檔案中,而模闆html通常以字元串的方式内聯到Javascript檔案中。目前最常用的前端建構工具就是glup,這裡有一篇初步應用的文章:前端 | gulp 打包 require.js 子產品依賴
2、頁面内部優化
關于頁面内部優化主要方向:樣式表放在頂部、腳本檔案放在底部、避免css表達式、把腳本的樣式表放在外部、移除重複腳本
關心性能的工程師都希望頁面能否盡快的展現在使用者面前,對于頁面中很多内容的頁面我們都希望内容能夠逐漸加載,為使用者提供可視化回饋。而将樣式表放在底部會導緻浏覽器阻止内容逐漸呈現。為避免當頁面變化時重繪頁面元素,浏覽器會阻塞頁面呈現,直到樣式表解析完畢(詳細内容可以檢視我的這篇部落格)。是以如果将樣式表放在頂部并不會減少資源的加載時間,它減少的是頁面的呈現時間。小米首頁曾經犯過這樣的錯誤:
将樣式表放在底部會阻塞頁面的逐漸呈現,而将script檔案放在頁面頂部同樣會阻塞頁面的逐漸呈現。script元素會阻塞後續内容的解析,因為script中可以同過document.write來更改頁面。解決的辦法就是将script标簽放在頁面底部。這樣既可以讓内容逐漸呈現,也可以提高下載下傳的并行度。如果我們确定不需要document.write那可以為script标簽加上asyn屬性(Ie中要加上defer)提高并行下載下傳度。
CSS表達式是ie支援的可以用來動态更改css屬性的一種方式,我們不需要了解太多,她的書寫方式如下,一旦在産品中發現expression關鍵字就要徹底消滅。
使用外部腳本和樣式這一條,我想凡是有點經驗的工程師都會這麼幹。
移除重複腳本:這條說的主要是避免在頁面中多次加入同一份Javascript代碼,如果我們的開發中有依賴管理的方式比如AMD、CMD,基本不會出現這種情況。
3、啟用緩存
關于緩存的使用這裡介紹兩套方案:expires/If-Modified-Since、Cache-Control/Etag;前者是HTTP1.0中的緩存方案,後者是HTTP1.1中緩存方案,若http頭部中同時出現二者,後者的優先級更高。
If-modified-since的方式通常被稱為條件Get。浏覽器緩存中儲存了一個檔案的副本,但需要向伺服器詢問此副本是否可用。If-Modified-Since是浏覽器将最後修改時間發送給伺服器,伺服器相應頭中Last-Modified進行對比;若If-Modified-Since = Last-Modified 則浏覽器讀取本地副本。此時響應狀态為304 Not Modified, 并不在發送響應體。
Expries:雖然使用條件GET和304響應能夠節省時間,但浏覽器跟伺服器端仍然要發送一次請求進行确認。通過明确設定副本的過期時間可以避免條件GET。當浏覽器發現響應頭中的expires時,會将過期時間和檔案一起儲存到緩存中去。在過期之前一直從緩存中讀取。expires頭使用一個特定的時間來指定緩存的有效期,他要求浏覽器與伺服器時間完全一緻。而且一旦過期,伺服器端配置中需要重新設頂一個過期時間。
ETag(實體标簽):是伺服器用于檢查浏覽器緩存有效性的一種機制。ETag在HTTP1.1中引入,ETag是唯一辨別了一個元件的一個特定版本的字元串。唯一的格式限制是這個字元串必須使用雙引号。如果浏覽器要驗證一個元件是否有效他會使用If-None-Match将etag字元串傳送給伺服器。如果ETag是比對的,伺服器端會傳回304.(如果實體資料需要根據User-Agent或Accept-Language來改變時,ETag提供了更高的靈活性)。對于使用伺服器叢集的網站來說,從一台伺服器到另一台伺服器,ETag通常是無法比對的。這是ETag的問題。而且即便同時使用If-Modified-Since和If-None-Match也并不能達到預期效果。解決方法總是有的:自定義Etag格式
Cache-Control:HTTP1.1引入了來代替Expires,它使用max-age指令來指定副本被緩存多久,該指令以秒為機關定義了一個更新窗,元件從被請求開始到現在的秒數小于設定值,則一直使用副本。避免了一次http請求。相比Expries,Cache-Control指令提供了更細粒度的控制。詳細内容請看大額同學的文章:透過浏覽器看HTTP緩存
4、減少下載下傳量
減少下載下傳量最有效的方式就是開啟gzip壓縮,gzip是GNU開發的一種免費格式。壓縮元件通過減小http響應的大小來加快響應速度。HTTP1.1通過使用Accept-Encoding來辨別支援的壓縮,如果伺服器看到這個辨別,會使用請求頭中的一種方式來壓縮響應。并通過Content-Encoding來通知web用戶端。很多網站會壓縮html檔案,實際上包括xml跟json在内的任何文本都可以壓縮,但圖檔和pdf不應該壓縮。根據經驗通常可以對大于1kb或2kb的檔案進行壓縮。壓縮通常能将響應的資料量減少70%。壓縮的成本在于:伺服器需要耗費額外的cpu進行壓縮,用戶端需要解壓縮。是以需要在cpu的消耗和資料塊的大小之間進行取舍。
5、優化網絡連接配接
網絡連接配接的優化主要有三個規則:使用CDN加速、減少DNS查找、避免重定向
CDN:CDN是地理上分布的web server的集合,用于更高效地釋出内容。通常基于網絡遠近來選擇給具體使用者服務的web server。 這縮短了資源的傳輸響應時間,有效提高web性能。
DNS用于映射主機名和IP位址,一般一次解析需要20~120毫秒。浏覽器會首先根據頁面的主機名進行域名解析,在有ISP傳回結果之前頁面不會加載任何内容,是以減少DNS查找可以有效降低等待時間。為達到更高的性能,DNS解析通常被多級别地緩存,如由ISP或區域網路維護的caching server,本地機器作業系統的緩存(如windows上的DNS Client Service),浏覽器。IE的預設DNS緩存時間為30分鐘,Firefox的預設緩沖時間是1分鐘。 我們能做的是盡量減少一個頁面的主機名,但要在浏覽器最大并行下載下傳數跟dns查找之間做權衡。根據雅虎的研究,最好将主機名控制在2-4個内。
重定向:将一個URL重新路由到另一個URL。重定向功能是通過301和302這兩個HTTP狀态碼完成的,如:
HTTP/1.1 301 Moved Permanently
Location: http://example.com/newuri
Content-Type: text/html
浏覽器自動重定向請求到Location指定的URL上,重定向的主要問題是降低了使用者體驗。 種最耗費資源、經常發生而很容易被忽視的重定向是URL的最後缺少/,導緻自動産生結尾斜線的原因是,浏覽器在進行get請求是必須指定一些路徑;如果沒有路徑它就會簡單的使用文檔根。(主機缺少結尾斜線是不會發生重定向:http://www.baidu.com)缺少結尾斜線發生重定向是很多web伺服器的預設行為。需要在伺服器端設定方可消除。以下圖檔是豆瓣的一個url請求:
雅虎的14條優化規則在很長的一段時間裡發揮着重要作用,随着技術的發展,單單這十四條原則已經不能夠滿足前端性能優化。在一些大公司出現了前端工程化這一概念,詳細内容可以參考一下這篇文章:前端性能優化工程化進階
參考資料:
web前端性能意思、關注重點、測試方案、
WEB站點性能優化實踐(加載速度提升2s)
HTTP協定三次握手過程
高性能WEB開發 - 為什麼要減少請求數,如何減少請求數!
我是如何對網站CSS進行架構的
圖示字型化淺談
利用ETag緩存優化請求
透過浏覽器看HTTP緩存
Web應用性能優化黃金法則——轉
您可以考慮給樹發個小額微信紅包以資鼓勵