gzip是GNUzip的縮寫,最先用於UNIX系統的文件壓縮。HTTP協議上的gzip編碼是一種用來改進web應用程序性能的技術,web服務器和客戶端(瀏覽器)必須共同支持gzip。目前主流的瀏覽器,Chrome,firefox,IE等都支持該協議。常見的服務器如Apache,Nginx,IIS一樣支持gzip。 gizp流程:javascript
Nginx中開啓gzip: css
HTTP/2是HTTP協議自1999年HTTP 1.1發佈後的首個更新,主要基於SPDY協議(是Google開發的基於TCP的應用層協議,用以最小化網絡延遲,提高網絡速度,優化用戶的網絡使用體驗)。 優化原理: 根據上文中說的資源合併問題,瀏覽器能夠同時創建有限個TCP鏈接,而每一個鏈接都要通過慢啓動
,三次握手
,鏈接創建
,HTTP1.1爲了解決這個問題推出了keep-alive,即保持鏈接不被釋放,可是真正的這些鏈接下載資源是一個線性的流程:一個資源的請求響應返回後,下一個請求才能發送。這被稱爲線頭阻塞,爲了完全解決此問題,HTTP2.0帶來了多路複用: html
如今回過頭來探討一下上文說的資源合併問題,有了HTTP2.0以後,咱們是否還須要合併資源,目前看須要遵循下面的原則:前端
中止合併文件 在HTTP/1.1中,CSS,JavaScript被壓縮到了一個文件,圖片被合併到了一張雪碧圖上。合併CSS、JavaScript和圖片極大地減小了HTTP的請求數,在HTTP/1.1中能得到顯著的性能提高。 可是,在HTTP/2.0中合併文件再也不是一個好的辦法。雖然合併依然能夠提升壓縮率,但它帶來了代價高昂的緩存失效。即便有一行代碼改變了,整個文件就要從新打包壓縮,瀏覽器也會強制從新加載新的文件。vue
儘可能不要在HTML裏內聯資源 非特殊的代碼(rem適配代碼,上報代碼等)以外,儘可能不要使用內聯資源,在極端狀況下,這確實可以減小給定網頁的HTTP請求數。可是,和文件合併同樣,HTTP/2優化時你不該該內聯文件。內聯意味着瀏覽器不能緩存單個的資源。若是你將全部頁面使用的CSS聲明嵌入了每個HTML文件,這些文件每次都要從服務端獲取。這致使用戶在訪問任何頁面時都要傳輸額外的字節。java
合併域名 拆分域名是讓瀏覽器創建更多TCP鏈接的一般手段,瀏覽器限制了單個服務器的鏈接數量,可是經過將網站上的資源切分到幾個域上,你能夠得到額外的TCP鏈接,可是每一個拆分的域名都會帶來額外的DNS查詢、握手,新鏈接的創建,根據HTTP2.0多路複用的原則:HTTP2採用多路複用是指,在同一個域名下,開啓一個TCP的connection,每一個請求以stream的方式傳輸,域名的合併能夠帶來更多的多路複用,以下圖在chrome的Network面板中查看HTTP2.0,注意protocol和ConnectID相同則表示啓用複用: react
<img src="data:image/gif;base64,R0..."/>
複製代碼
在頁面使用的背景類圖片icon類圖片,很少且比較小的狀況下,能夠把圖片轉成base64編碼嵌入到html頁面或者CSS文件中,這樣能夠減小頁面的HTTP請求數。須要注意的是,要保證圖片較小,通常超過5kb的就不推薦base64嵌入顯示了。爲何是5kb?。 同時,採用Webpack的url-loader能夠幫咱們在不影響代碼可讀性的狀況下,解決base64字符串問題。android
IconFont技術起源於Web領域的Web Font技術,它是把一些簡單的圖標製做成字體,而後讓圖標變成和字體同樣使用,Icon 的設計和使用在近幾年的發展中,也經歷了由當初的 img 方案 到現現在的 svg 方案,有如下優勢:webpack
首屏的快速顯示,能夠大大提高用戶對頁面速度的感知,所以應儘可能針對首屏的快速顯示作優化,基於聯通3G網絡平均338KB/s(2.71Mb/s),因此首屏資源不該超過1014KB,剝離首屏須要的資源,非首屏的資源單獨合併,採用懶加載。這個原則適用上文的資源合併和加載中的場景。git
將不影響首屏的資源和當前屏幕資源不用的資源放到用戶須要時才加載,能夠大大提高重要資源的顯示速度和下降整體流量,對於移動web端常見的多tab頁面,Webpack的CodeSplitting幫助咱們更加便捷實現按需加載。
不用多說,在目前流量費用還算比較高昂的狀況下,幫助用戶節省更多的流量能夠避免用戶的投訴,爲了保證頁面內容最小化,加速頁面渲染,儘量節省首屏網絡流量,頁面中的圖片資源推薦使用懶加載實現,在頁面滾動時動態載入圖片。
CDN是將源站內容分發至最接近用戶的節點,使用戶可就近取得所需內容,提升用戶訪問的響應速度和成功率。解決因分佈、帶寬、服務器性能帶來的訪問延遲問題,適用於站點加速、點播、直播等場景。 對於web頁面來講,將項目的js,css等靜態資源存放在CDN是一個重要的優化手段,加入全部資源統一打包放在同一個域名下,很難達到用戶就近獲取的優點(目前最佳實踐是html頁面採用一個域名,靜態資源文件採用CDN域名),所謂靜態資源便是能夠被瀏覽器緩存的資源,而對於html頁面,因爲是js和css等連接的入口,一般不採用緩存。經常使用的阿里雲CDN和騰訊雲CDN都有開放接口,開發者能夠按需選擇。
此預加載主要分爲兩個部分,一種是採用原生瀏覽器支持的API來對頁面的一些資源進行預先拉取或者加載,另外一種是經過本身寫邏輯來加載一些重要的資源,當即下面內容的前提是要當即目前移動web常見的hybrid架構,webview外殼+H5頁面:
DNS 做爲互聯網的基礎協議,其解析的速度彷佛很容易被網站優化人員忽視。如今大多數新瀏覽器已經針對DNS解析進行了優化,典型的一次DNS解析須要耗費 20-120 毫秒,減小DNS解析時間和次數是個很好的優化方式。DNS Prefetching 是讓具備此屬性的域名不須要用戶點擊連接就在後臺解析,而域名解析和內容載入是串行的網絡操做,因此這個方式能 減小用戶的等待時間,提高用戶體驗 。 html <link rel="dns-prefetch" href="//haitao.nos.netease.com">
二者都是以<link rel="preload"> 和 <link rel="prefetch">
做爲引入方式。
一個基本的用法是提早加載資源,告訴瀏覽器預先請求當前頁須要的資源,從而提升這些資源的請求優先級,加載可是不運行,佔用瀏覽器對同一個域名的併發數:
<link rel="preload" href="a.js" as="script" onload="preloadLoad()">
複製代碼
用法是瀏覽器會在空閒的時候,下載資源, 並緩存起來。當有頁面使用的時候,直接從緩存中讀取。其實就是把決定是否和什麼時間加載這個資源的決定權交給瀏覽器。
<link rel="prefetch" href="a.js">
複製代碼
遺憾的是對於這兩個接口,移動端的瀏覽器支持性很很差,這也是沒有廣泛推廣開來的緣由。
何時使用Preload,何時使用Prefetch能夠總結以下:對於當前頁面頗有必要的資源使用 preload,對於可能在未來的頁面中使用的資源使用 prefetch。關於Preload 和 Prefetch能夠 參考這裏。另外還有Prerender,subresource,Preconnect屬性,因爲目前能支持到這些屬性的機型太少,這裏就不在贅述了。
關於業務邏輯的預加載,在這裏我能夠舉一個微信小程序的例子。小程序主要分爲渲染層和邏輯層,邏輯層有iOS或者Android的JavaScriptcore來運行,渲染層由各自的webview組件負責渲染。咱們用戶實際體驗到的UI仍是跑在咱們的webview裏面,這個和大多數H5頁面的渲染用的是一個組件。可是爲何咱們體驗小程序會比H5頁面要快不少?尤爲是新開頁面時?
小程序在啓動時,會預先加載全部頁面邏輯代碼進內存,在 a頁面跳轉至 b頁面 時,能夠在內存中直接運行而無需在發送資源請求,a頁面的邏輯代碼 Javascript 數據也不會從內存中消失。b頁面甚至能夠直接訪問 a頁面中的數據,整個壞境在一個大的上下文中。 固然這裏你可能會有疑問?假如用戶不會進入page2,那加載page2的邏輯代碼豈不是浪費?這裏就會牽扯到一個用戶行爲預測的問題,在小程序的架構中,整個邏輯代碼是統一在一個包裏,微信是統一將這些文件下載並加載到內存中,這可能會涉及到一些浪費,可是對於提速來說收益大於弊端的。固然小程序頁提供出 分包策略來優化這些問題。藉助小程序的思路,咱們的移動web一樣適用這種預加載優化邏輯:
在多tab的單頁應用中,咱們能夠在用戶打開首屏以後,預先加載其餘tab的資源。例如用戶進入時在推薦tab,這時就能夠預先加載訂單,個人 這兩個tab的資源了,當用戶點擊訂單時,頁面的展示就會快一些。
預加載數據的時機最好是在空閒時,什麼是空閒時呢?咱們分析一下打開一個H5頁面的流程:
從圖上能夠看到,利用閒時能夠作的事情有不少,預加載數據是一個典型的優化手段,提早把新頁面所須要的數據加載好,在新頁面打開後,能夠直接用數據來進行渲染,固然這裏涉及到的跨頁面數據通訊,咱們能夠利用localStroage來實現。我利用閒時來作更多事情的前提是閒時夠長,但這本書也不是一個很好的現象,儘可能的減小閒時,也是咱們須要作的一項優化,例如咱們來減小webview的加載時間,這就須要提早加載webview,此項優化大可能是由native端來完成:
上文說了瀏覽器緩存的基礎知識,既然是基礎,那就說明必須掌握,下面來講一些進階篇的利用緩存來優化頁面:
HTML5 LocalStorage能夠看作是增強版的cookie,數據存儲大小提高,有更好的彈性以及架構,能夠將數據寫入到本機的ROM中,還能夠在關閉瀏覽器後再次打開時恢復數據,以減小網絡流量,平常使用localStorage來優化咱們的頁面大概有如下幾種場景:
離線包技術能夠說是並不算很新的技術了,各個業務都有在使用,也都有本身的一套hybrid離線包系統,關鍵點在於離線包的打包,同時對文件加密/簽名,更新離線包(增量) ,安全教研以及容錯機制等等,在這裏列舉一些大廠的離線包方案來參考:
提到緩存,那就不得不提近幾年比較火的Service Worker了:
做爲一個比較新的技術,你們能夠把 Service Worker 理解爲一個介於客戶端和服務器之間的一個代理服務器。在 Service Worker 中咱們能夠作不少事情,好比攔截客戶端的請求、向客戶端發送消息、向服務器發起請求等等,其中最重要的做用之一就是離線資源緩存。 Service Worker的主要複雜點在於不斷地對緩存策略的調整,筆者在這裏就不過多展開,能夠參考一下淘寶 Service worker實戰。在先後端分離以後,後端語言的模板功能被弱化,整個頁面的渲染基本上都由前端 js 動態渲染,但這樣對於一些應用來講是有缺陷的。好比須要 SEO 的,須要打開頁面不用等待就能看到頁面的,另外前端頁面展現過分依賴js和css邏輯執行,在極端狀況或者網絡較差,手機性能低下(尤爲在低端Android機型較爲明顯)時,白屏時間較長,這時服務端渲染便應用而生,至於爲何是Nodejs,做爲一個前端,難道還要用Java麼。。?
若是你說服務端渲染和早期web框架,例如SSH,JSP servlet,PHP等等同樣的話,那我只能說呵呵,目前的服務端渲染和早期的框架是有本質區別的:
若是你的項目用的是React或者是vue,那麼下面兩個現場的開源框架是不錯的選擇。
固然,你也能夠本身實現一套本身的服務端渲染框架,通常須要關注這些問題:
使用Nodejs的服務端渲染的一大優點就是代碼同構,這使得一個項目能夠分別部署成走線上正常前端渲染版本,和走服務端渲染版本,這樣能夠更好的作到容災機制,當任何一種分之掛掉以後,能夠直接走另外一個版本,提升穩定性。這也同構的魅力所在!由於在同構直出宕掉的時候,還有前端渲染頁面能夠提供正常的服務。
雖說服務端渲染這類優化確實能夠提高必定的頁面首屏時間,可是也是須要成本的,在前端開發接管了Node做爲中間層時,須要額外的機器資源部署,而且一旦接觸到後端,容災機制,內存管理等性能指標都須要關注,這對於當前的業務系統架構可能須要有必定的調整,因此仍是要斟酌來使用。
終於回到咱們前端的老本家了,若是說前面的優化都是在框架,邏輯層面的優化,或者是參考後端,客戶端的優化思路,那麼真正涉及到UI渲染的優化纔是咱們做爲前端工程師的立身之本了。
拋開首屏加速,真正讓用戶體驗web頁面的另外一個很重要的部分就是用戶行爲交互了,這包括用戶的點擊相應,滾動流暢度,動畫是否卡頓流暢度等等,這些關於用戶交互性的優化在已往的PC端可能不是很被重視,由於PC瀏覽器的性能要遠遠大於手機端,可是到了移動web就不同了,用戶都但願移動web能有PC端同樣的性能。
目前主流的Android硬件配置能夠說是甩iPhone幾條街了,那爲何高配置卻得不到好的體驗呢?關鍵兩類機型的操做系統上的優化程度,其中一個緣由就是iOS操做系統採用執行率較高的Object-c語言,大部分硬件接口能夠直接調用和運行,而Android則採用Java語言,由於虛擬機的存在,雖然跨平臺性提高了,可是經過虛擬機在和系統硬件交互,執行效率就低了不少,固然這只是其中一個緣由。那麼,咱們移動web主要優化的羣體就是Android機型了。
目前大多數設備的屏幕刷新頻率爲60次/秒,每一幀所消耗的時間約爲16ms(1000 ms / 60 = 16.66ms),這16ms就是渲染幀的時長,所謂渲染幀是指瀏覽器一次完整繪製過程,幀之間的時間間隔是DOM視圖更新的最小間隔,但實際上,瀏覽器還有一些整理工做要作,所以開發者所作的全部工做須要在10ms內完成。 若是不能完成,幀率將會降低,網頁會在屏幕上抖動,也就是一般所說的卡頓,這會對用戶體驗產生嚴重的負面影響。因此若是一個頁面中有動畫效果或者用戶正在滾動頁面,那麼瀏覽器渲染動畫或頁面的速率也要儘量地與設備屏幕的刷新頻率保持一致,以保證良好的用戶體驗。在這一個間隔內,瀏覽器可能須要作如下事情:
- 腳本執行(JavaScript):腳本形成了須要重繪的改動,好比增刪 DOM、請求動畫等
- 樣式計算(CSS Object Model):級聯地生成每一個節點的生效樣式。
- 佈局(Layout):計算佈局,執行渲染算法
- 重繪(Paint):各層分別進行繪製(好比 3D 動畫)
- 合成(Composite):合成各層的渲染結果
複製代碼
在上面瀏覽器須要作的這些事情中,會引起不一樣程度的重繪和重排,而重繪和重排正式影響流暢的重要因素:
部分渲染樹(或者整個渲染樹)須要從新分析而且節點尺寸須要從新計算,這被稱爲重排。
因爲節點的幾何屬性發生改變或者因爲樣式發生改變,例如改變元素背景色時,屏幕上的部份內容須要更新,這樣的更新被稱爲重繪。
重排和重繪代價是高昂的,它們會破壞用戶體驗,而且讓UI展現很是遲緩,可是每次重排,必然會致使重繪,而每次重繪並不必定會發生重排,咱們須要在如下幾種場景來減小重排的發生: 當頁面佈局和幾何屬性改變時就須要迴流。下述狀況會發生瀏覽器迴流:
提高動畫流暢度的另外一個重要因素是讓瀏覽器變得智能起來,好在瀏覽器給咱們提供了這個接口requestAnimationFrame,經過這個API,能夠告訴瀏覽器某個JavaScript代碼要執行動畫,瀏覽器收到通知後,則會運行這些代碼的時候進行優化,它會確保JS儘早在每一幀的開始執行,實現流暢的效果,而再也不須要開發人員煩心刷新頻率的問題了:
function animationWidth() {
var div = document.getElementById('box');
div.style.width = parseInt(div.style.width) + 1 + 'px';
if(parseInt(div.style.width) < 200) {
requestAnimationFrame(animationWidth)
}
}
requestAnimationFrame(animationWidth);
複製代碼
requestIdleCallback的出現伴隨着React 16 的Fiber特性,他的使用場景是當用戶在作負責交互時,不但願由於一些不重要的任務(如統計上報)致使用戶感受到卡頓的話,就應該考慮使用了,由於requestIdleCallback回調的執行的前提條件是當前瀏覽器處於空閒狀態,可是須要注意的是不要在requestIdleCallback操做任何DOM,這違背了這個接口的設計原則。
requestIdelCallback(myNonEssentialWork);
function myNonEssentialWork (deadline) {
// deadline.timeRemaining()能夠獲取到當前幀剩餘時間
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
doWorkIfNeeded();
}
if (tasks.length > 0){
requestIdleCallback(myNonEssentialWork);
}
}
複製代碼
在你使用dominnerHTML方法來插入大量dom節點時,不妨試試fragment,fragment文檔片斷是個輕量級的document對象,它的設計初衷就是爲了完成這類任務——更新和移動節點。文檔片斷的一個便利的語法特性是當你附加一個片段到節點時,實際上被添加的是該片段的子節點,而不是片段自己。只觸發了一次重排,並且只訪問了一次實時的DOM。
長列表滾動在移動端是一種很是常見的交互模式,例如feeds流,圖片流等等,這些列表的滾動流暢度優化對用戶體驗的提高是很是重要的,基於目前的優化思路,藉助dom複用的方案,相似iOS的UITableView或者Android的recyclerview原理,在列表滾動時,只保證視窗區域內的dom節點存在,在有限的dom節點內實現滾動,而不在建立新的節點,在用戶不斷下拉翻頁的過程當中,保證整個頁面有限的dom元素來減小內存的消耗,原理以下圖:
複用的dom: 採用這一個方案的前端是藉助瀏覽器的onscroll事件來作邏輯處理,可是問題在於有些機型例如iOS的UIWebview下,onscroll不能實時觸發,這就給優化帶來了難題,由此引起出了模擬滾動:結論是若是要採用模擬滾動,能夠解決onscroll不實時觸發的問題,從而實現長列表的複用的優化,可是帶來新的問題就是模擬滾動自己也是dom的重繪,增長額外的性能消耗,達到有優化效果並不理想,好在iOS的新版WKwebview解決了onscroll問題,讓開發者有了更好的選擇。
當持續觸發事件時,必定時間段內沒有再觸發事件,事件處理函數纔會執行一次,若是設定的時間到來以前,又一次觸發了事件,就從新開始延時。以下圖,持續觸發scroll事件時,並不執行handle函數,當1000毫秒內沒有觸發scroll事件時,纔會延時觸發scroll事件。
function debounce(fn, wait) {
var timeout = null;
return function() {
if(timeout !== null)
clearTimeout(timeout);
timeout = setTimeout(fn, wait);
}
}
// 處理函數
function handle() {
console.log(Math.random());
}
// 滾動事件
window.addEventListener('scroll', debounce(handle, 1000));
複製代碼
動畫卡頓是在移動web開發時常常遇到的問題,解決這個問題通常會用到CSS3硬件加速。硬件加速這個名字感受上很高大上,其實它作的事情能夠簡單歸納爲:經過GPU進行渲染,解放CPU,咱們能夠利用GPU的圖形層,將負責的動畫操做放在這個層,如何開啓?
webkit-transform: translateZ(0);
複製代碼
強制把須要動畫的dom的對象 ,放置在GPU的layout層來緩存從而達到任何移動,大小變化都在這個層。 經過開啓GPU硬件加速雖然能夠提高動畫渲染性能或解決一些棘手問題,但使用仍需謹慎,使用前必定要進行嚴謹的測試,不然它反而會大量佔用瀏覽網頁用戶的系統資源,尤爲是在移動端,肆無忌憚的開啓GPU硬件加速會致使大量消耗內存,千萬不要* {webkit-transform: translateZ(0);}
。
本文在性能優化的基礎上,將移動web的性能點逐步展開和深刻,內容比較多,指望各位開發者能真正實踐並進行不斷嘗試,總之:
技術就是在於不斷折騰,願各位在踩坑的道路上一路順風!
最後,向你們推薦一門慕課網的實戰課程《移動Web APP開發之實戰美團外賣》(當即學習),但願小夥伴們能經過這門課程收穫滿滿,祝你們學習進步。