引子
網際網路有一項著名的8秒原則。使用者在訪問Web網頁時,如果時間超過8秒就會感到不耐煩,如果載入需要太長時間,他們就會放棄訪問。大部分使用者希望網頁能在2秒之內就完成載入。事實上,載入時間每多1秒,你就會流失7%的使用者。8秒並不是準確的8秒鐘,只是向網站開發者表明了載入時間的重要性。那我們如何優化頁面效能,提高頁面載入速度呢?這是本文主要要探討的問題,然而效能優化是個綜合性問題,沒有標準答案,想要面面俱到羅列出來,並非易事。
本文只關注一些核心要點,以下是我結合慕課網課程《Web前端效能優化》,總結效能優化常見的辦法:
如果覺得文章對你有些許幫助,歡迎在我的GitHub部落格點贊和關注,感激不盡!
一、資源壓縮與合併
主要包括這些方面:html壓縮、css 壓縮、js的壓縮和混亂和檔案合併。 資源壓縮可以從檔案中去掉多餘的字元,比如回車、空格。你在編輯器中寫程式碼的時候,會使用縮排和註釋,這些方法無疑會讓你的程式碼簡潔而且易讀,但它們也會在文件中新增多餘的位元組。
1.html壓縮
html程式碼壓縮就是壓縮這些在文字檔案中有意義,但是在HTML中不顯示的字元,包括空格,製表符,換行符等,還有一些其他意義的字元,如HTML註釋也可以被壓縮。
如何進行html壓縮:
- 使用線上網站進行壓縮(開發過程中一般不用)
- nodejs 提供了html-minifier工具
- 後端模板引擎渲染壓縮
2.css程式碼壓縮:
css程式碼壓縮簡單來說就是無效程式碼刪除和css語義合併
如何進行css壓縮:
- 使用線上網站進行壓縮(開發過程中一般不用)
- 使用html-minifier工具
- 使用clean-css對css壓縮
3.js的壓縮和混亂
js的壓縮和混亂主要包括以下這幾部分:
- 無效字元的刪除
- 剔除註釋
- 程式碼語義的縮減和優化
- 程式碼保護(程式碼邏輯變得混亂,降低程式碼的可讀性,這點很重要)
如何進行js的壓縮和混亂
- 使用線上網站進行壓縮(開發過程中一般不用)
- 使用html-minifier工具
- 使用uglifyjs2對js進行壓縮
其實css壓縮與js的壓縮和混亂比html壓縮收益要大得多,同時css程式碼和js程式碼比html程式碼多得多,通過css壓縮和js壓縮帶來流量的減少,會非常明顯。所以對大公司來說,html壓縮可有可無,但css壓縮與js的壓縮和混亂必須要有!
4.檔案合併
從上圖可以看出不合並請求有以下缺點:- 檔案與檔案之間有插入的上行請求,增加了N-1個網路延遲
- 受丟包問題影響更嚴重
- keep-alive方式可能會出現狀況,經過代理伺服器時可能會被斷開,也就是說不能一直保持keep-alive的狀態
壓縮合並css和js可以減少網站http請求的次數,但合併檔案可能會帶來問題:首屏渲染和快取失效問題。那該如何處理這問題呢?----公共庫合併和不同頁面的合併。
如何進行檔案合併
- 使用線上網站進行檔案合併
- 使用nodejs實現檔案合併(gulp、fis3)
二、非核心程式碼非同步載入非同步載入的方式
1、非同步載入的方式
非同步載入的三種方式——async和defer、動態指令碼建立
① async方式
- async屬性是HTML5新增屬性,需要Chrome、FireFox、IE9+瀏覽器支援
- async屬性規定一旦指令碼可用,則會非同步執行
- async屬性僅適用於外部指令碼
- 如果是多個指令碼,該方法不能保證指令碼按順序執行
<script type="text/javascript" src="xxx.js" async="async"></script>
複製程式碼
② defer方式
- 相容所有瀏覽器
- defer屬性規定是否對指令碼執行進行延遲,直到頁面載入為止
- 如果是多個指令碼,該方法可以確保所有設定了defer屬性的指令碼按順序執行
- 如果指令碼不會改變文件的內容,可將defer屬性加入到script標籤中,以便加快處理文件的速度
③動態建立script標籤 在還沒定義defer和async前,非同步載入的方式是動態建立script,通過window.onload方法確保頁面載入完畢再將script標籤插入到DOM中,具體程式碼如下:
function addScriptTag(src){
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function(){
addScriptTag("js/index.js");
}
複製程式碼
2、非同步載入的區別
1)defer是在HTML解析完之後才會執行,如果是多個,按照載入的順序依次執行
2)async是在載入完之後立即執行,如果是多個,執行順序和載入順序無關
其中藍色線代表網路讀取,紅色線代表執行時間,這倆都是針對指令碼的;綠色線代表 HTML 解析。
三、利用瀏覽器快取
對於web應用來說,快取是提升頁面效能同時減少伺服器壓力的利器。
瀏覽器快取型別
1.強快取:不會向伺服器傳送請求,直接從快取中讀取資源,在chrome控制檯的network選項中可以看到該請求返回200的狀態碼,並且size顯示from disk cache或from memory cache;
相關的header:
Expires :response header裡的過期時間,瀏覽器再次載入資源時,如果在這個過期時間內,則命中強快取。它的值為一個絕對時間的GMT格式的時間字串, 比如Expires:Thu,21 Jan 2018 23:39:02 GMT
Cache-Control :這是一個相對時間,在配置快取的時候,以秒為單位,用數值表示。當值設為max-age=300時,則代表在這個請求正確返回時間(瀏覽器也會記錄下來)的5分鐘內再次載入資源,就會命中強快取。比如Cache-Control:max-age=300,
簡單概括:其實這兩者差別不大,區別就在於 Expires 是http1.0的產物,Cache-Control是http1.1的產物,兩者同時存在的話,Cache-Control優先順序高於Expires;在某些不支援HTTP1.1的環境下,Expires就會發揮用處。所以Expires其實是過時的產物,現階段它的存在只是一種相容性的寫法。強快取判斷是否快取的依據來自於是否超出某個時間或者某個時間段,而不關心伺服器端檔案是否已經更新,這可能會導致載入檔案不是伺服器端最新的內容,那我們如何獲知伺服器端內容較客戶端是否已經發生了更新呢?此時我們需要協商快取策略。
2.協商快取:向伺服器傳送請求,伺服器會根據這個請求的request header的一些引數來判斷是否命中協商快取,如果命中,則返回304狀態碼並帶上新的response header通知瀏覽器從快取中讀取資源;另外協商快取需要與cache-control共同使用。
相關的header:
①Last-Modified和If-Modified-Since:當第一次請求資源時,伺服器將資源傳遞給客戶端時,會將資源最後更改的時間以“Last-Modified: GMT”的形式加在實體首部上一起返回給客戶端。
Last-Modified: Fri, 22 Jul 2016 01:47:00 GMT
複製程式碼
客戶端會為資源標記上該資訊,下次再次請求時,會把該資訊附帶在請求報文中一併帶給伺服器去做檢查,若傳遞的時間值與伺服器上該資源最終修改時間是一致的,則說明該資源沒有被修改過,直接返回304狀態碼,內容為空,這樣就節省了傳輸資料量 。如果兩個時間不一致,則伺服器會發回該資源並返回200狀態碼,和第一次請求時類似。這樣保證不向客戶端重複發出資源,也保證當伺服器有變化時,客戶端能夠得到最新的資源。一個304響應比一個靜態資源通常小得多,這樣就節省了網路頻寬。
但last-modified 存在一些缺點:
Ⅰ.某些服務端不能獲取精確的修改時間
Ⅱ.檔案修改時間改了,但檔案內容卻沒有變
既然根據檔案修改時間來決定是否快取尚有不足,能否可以直接根據檔案內容是否修改來決定快取策略?----ETag和If-None-Match
②ETag和If-None-Match:Etag是上一次載入資源時,伺服器返回的response header,是對該資源的一種唯一標識,只要資源有變化,Etag就會重新生成。瀏覽器在下一次載入資源向伺服器傳送請求時,會將上一次返回的Etag值放到request header裡的If-None-Match裡,伺服器只需要比較客戶端傳來的If-None-Match跟自己伺服器上該資源的ETag是否一致,就能很好地判斷資源相對客戶端而言是否被修改過了。如果伺服器發現ETag匹配不上,那麼直接以常規GET 200回包形式將新的資源(當然也包括了新的ETag)發給客戶端;如果ETag是一致的,則直接返回304知會客戶端直接使用本地快取即可。
兩者之間對比: 首先在精確度上,Etag要優於Last-Modified。Last-Modified的時間單位是秒,如果某個檔案在1秒內改變了多次,那麼他們的Last-Modified其實並沒有體現出來修改,但是Etag每次都會改變確保了精度;如果是負載均衡的伺服器,各個伺服器生成的Last-Modified也有可能不一致。 第二在效能上,Etag要遜於Last-Modified,畢竟Last-Modified只需要記錄時間,而Etag需要伺服器通過演算法來計算出一個hash值。 第三在優先順序上,伺服器校驗優先考慮Etag
快取的機制
強制快取優先於協商快取進行,若強制快取(Expires和Cache-Control)生效則直接使用快取,若不生效則進行協商快取(Last-Modified / If-Modified-Since和Etag / If-None-Match),協商快取由伺服器決定是否使用快取,若協商快取失效,那麼代表該請求的快取失效,重新獲取請求結果,再存入瀏覽器快取中;生效則返回304,繼續使用快取。主要過程如下:
使用者行為對瀏覽器快取的影響
1.位址列訪問,連結跳轉是正常使用者行為,將會觸發瀏覽器快取機制;
2.F5重新整理,瀏覽器會設定max-age=0,跳過強快取判斷,會進行協商快取判斷;
3.ctrl+F5重新整理,跳過強快取和協商快取,直接從伺服器拉取資源。
如果想了解更多快取機制,請猛戳 深入理解瀏覽器的快取機制
四、使用CDN
大型Web應用對速度的追求並沒有止步於僅僅利用瀏覽器快取,因為瀏覽器快取始終只是為了提升二次訪問的速度,對於首次訪問的加速,我們需要從網路層面進行優化,最常見的手段就是CDN(Content Delivery Network,內容分發網路)加速。通過將靜態資源(例如javascript,css,圖片等等)快取到離使用者很近的相同網路運營商的CDN節點上,不但能提升使用者的訪問速度,還能節省伺服器的頻寬消耗,降低負載。
CDN是怎麼做到加速的呢?
其實這是CDN服務商在全國各個省份部署計算節點,CDN加速將網站的內容快取在網路邊緣,不同地區的使用者就會訪問到離自己最近的相同網路線路上的CDN節點,當請求達到CDN節點後,節點會判斷自己的內容快取是否有效,如果有效,則立即響應快取內容給使用者,從而加快響應速度。如果CDN節點的快取失效,它會根據服務配置去我們的內容源伺服器獲取最新的資源響應給使用者,並將內容快取下來以便響應給後續訪問的使用者。因此,一個地區內只要有一個使用者先載入資源,在CDN中建立了快取,該地區的其他後續使用者都能因此而受益。
五、預解析DNS
資源預載入是另一個效能優化技術,我們可以使用該技術來預先告知瀏覽器某些資源可能在將來會被使用到。 通過 DNS 預解析來告訴瀏覽器未來我們可能從某個特定的 URL 獲取資源,當瀏覽器真正使用到該域中的某個資源時就可以儘快地完成 DNS 解析。例如,我們將來可從 example.com 獲取圖片或音訊資源,那麼可以在文件頂部的 標籤中加入以下內容:
<link rel="dns-prefetch" href="//example.com">
複製程式碼
當我們從該 URL 請求一個資源時,就不再需要等待 DNS 的解析過程。該技術對使用第三方資源特別有用。通過簡單的一行程式碼就可以告知那些相容的瀏覽器進行 DNS 預解析,這意味著當瀏覽器真正請求該域中的某個資源時,DNS 的解析就已經完成了,從而節省了寶貴的時間。 另外需要注意的是,瀏覽器會對a標籤的href自動啟用DNS Prefetching,所以a標籤裡包含的域名不需要在head中手動設定link。但是在HTTPS下不起作用,需要meta來強制開啟功能。這個限制的原因是防止竊聽者根據DNS Prefetching推斷顯示在HTTPS頁面中超連結的主機名。下面這句話作用是強制開啟a標籤域名解析
<meta http-equiv="x-dns-prefetch-control" content="on">
複製程式碼
給大家推薦一個好用的BUG監控工具Fundebug,歡迎免費試用!
歡迎關注公眾號:前端工匠,你的成長我們一起見證!
參考