前端優化,其實就幾句話: javascript
傳輸層面:減小請求數,下降請求量
執行層面:減小重繪&迴流
傳輸層面的歷來都是優化的核心點,而這個層面的優化要對瀏覽器有一個基本的認識,好比:css
① 網頁自上而下的解析渲染,邊解析邊渲染,頁面內CSS文件會阻塞渲染,異步CSS文件會致使迴流html
② 瀏覽器在document下載結束會檢測靜態資源,新開線程下載(有併發上限),在帶寬限制的條件下,無序併發會致使主資源速度降低,從而影響首屏渲染前端
③ 瀏覽器緩存可用時會使用緩存資源,這個時候能夠避免請求體的傳輸,對性能有極大提升java
衡量性能的重要指標爲首屏載入速度(指頁面能夠看見,不必定可交互),影響首屏的最大因素爲請求,因此請求是頁面真正的殺手,通常來講咱們會作這些優化:webpack
① 合併樣式、腳本文件web
② 合併背景圖片json
③ CSS3圖標、Icon Font前端工程化
① 開啓GZip瀏覽器
② 優化靜態資源,jQuery->Zepto、閹割IScroll、去除冗餘代碼
③ 圖片無損壓縮
④ 圖片延遲加載
⑤ 減小Cookie攜帶
不少時候,咱們也會採用相似「時間換空間、空間換時間」的作法,好比:
① 緩存爲王,對更新較緩慢的資源&接口作緩存(瀏覽器緩存、localsorage、application cache這個坑多)
② 按需加載,先加載主要資源,其他資源延遲加載,對非首屏資源滾動加載
③ fake頁技術,將頁面最初須要顯示Html&Css內聯,在頁面所需資源加載結束前至少可看,理想狀況是index.html下載結束即展現(2G 5S內)
④ CDN
從工程的角度來看,上述優化點半數以上是重複的,通常在發佈時候就直接使用項目構建工具作掉了,還有一些只是簡單的服務器配置,開發時不須要關注。
能夠看到,咱們所作的優化都是在減小請求數,下降請求量,減少傳輸時的耗時,或者經過一個策略,優先加載首屏渲染所需資源,然後再加載交互所需資源(好比點擊時候再加載UI組件),Hybrid APP這方面應該儘量多的將公共靜態資源放在native中,好比第三方庫,框架,UI甚至城市列表這種經常使用業務數據。
有一些網站初期比較快,可是隨着量的積累,BUG愈來愈多,速度也愈來愈慢,一些前端會使用上述優化手段作優化,可是收效甚微,一個比較典型的例子就是代碼冗餘:
① 以前的CSS所有放在了一個文件中,新一輪的UI樣式優化,新老CSS難以拆分,CSS體量會增長,若是有業務團隊使用了公共樣式,狀況更不容樂觀;
② UI組件更新,可是若是有業務團隊脫離接口操做了組件DOM,將致使新組件DOM更新受限,最差的狀況下,用戶會加載兩個組件的代碼;
③ 胡亂使用第三方庫、組件,致使頁面加載大量無用代碼;
......
以上問題會不一樣程度的增長資源下載體量,若是聽之任之會產生一系列工程問題:
① 頁面關係錯綜複雜,需求迭代容易出BUG;
② 框架每次升級都會致使額外的請求量,常加載一些業務不須要的代碼;
③ 第三方庫氾濫,且難以維護,有BUG也改不了;
④ 業務代碼加載大量異步模塊資源,頁面請求數增多;
......
爲求快速佔領市場,業務開發時間每每緊迫,使用框架級的HTML&CSS、繞過CSS Sprite使用背景圖片、引入第三方工具庫或者UI,會常常發生。當遇到性能瓶頸時,若是不從根源解決問題,用傳統的優化手段作頁面級別的優化,會出現很快頁面又被玩壞的狀況,幾回優化結束後我也在思考一個問題:
前端每次性能優化的手段皆大同小異;代碼的可維護性也基本是在細分職責; 既然每次優化的目的是相同的,每次實現的過程是類似的,而每次從新開發項目又基本是要重蹈覆轍的,那麼工程化、自動化多是這一切問題的最終答案
工程問題在項目積累到必定量後可能會發生,通常來講會有幾個現象預示着工程問題出現了:
① 代碼編寫&調試困難
② 業務代碼很差維護
③ 網站性能廣泛很差
④ 性能問題重複出現,而且有不可修復之勢
像上面所描述狀況,就是一個典型的工程問題;定位問題、發現問題、解決問題是咱們處理問題的手段;而如何防止同一類型的問題重複發生,即是工程化須要作的事情,簡單說來,優化是解決問題,工程化是避免問題,今天咱們就站在工程化的角度來解決一些前端優化問題,防止其死灰復燃。
解決冗餘便拋開了歷史的包袱,是前端優化的第一步也是比較難的一步,但模塊拆分也將全站分紅了不少小的模塊,載入的資源分散會增長請求數;若是所有合併,會致使首屏加載不須要的資源,也會致使下一個頁面不能使用緩存,如何作出合理的入口資源加載規則,如何合理的善用緩存,是前端優化的第二步。
通過幾回性能優化對比,得出了一個較優的首屏資源加載方案:
① 核心框架層:mvc骨架、異步模塊加載器(require&seajs)、工具庫(zepto、underscore、延遲加載)、數據請求模塊、核心依賴UI(header組件消息類組件)
② 業務公共模塊:入口文件(require配置,初始化工做、業務公共模塊)
③ 獨立的page.js資源(包含template、css),會按需加載獨立UI資源
④ 全局css資源
這裏若是追求極致,libs.js、main.css與main.js能夠選擇合併,劃分結束後即可以決定靜態資源緩存策略了。
資源緩存是爲二次請求加速,比較經常使用的緩存技術有:
① 瀏覽器緩存
② localstorage緩存
③ application緩存
application緩存更新一塊很差把握容易出問題,因此更多的是依賴瀏覽器以及localstorage,首先說下瀏覽器級別的緩存。
只要服務器配置,瀏覽器自己便具備緩存機制,若是要使用瀏覽器機制做緩存,勢必關心一個什麼時候更新資源問題,咱們通常是這樣作的:
<script type="text/javascript" src="libs.js?t=20151025"></script>
這樣作要求必須先發布js文件,才能發佈html文件,不然讀不到最新靜態文件的。
一個比較尷尬的場景是libs.js是框架團隊甚至第三方公司維護的,和業務團隊的index.html是兩個團隊,互相的發佈是沒有關聯的,因此這二者的發佈順序是不能保證的,因而轉向開始使用了MD5的方式。
爲了解決以上問題咱們開始使用md5碼的方式爲靜態資源命名:
<script type="text/javascript" src="libs_md5_1234.js"></script>
每次框架更新便不作文件覆蓋,直接生成一個惟一的文件名作增量發佈,這個時候若是框架先發布,待業務發佈時便已經存在了最新的代碼;當業務先發布框架沒有新的時,便繼續沿用老的文件,一切都很美好,雖然業務開發偶爾會抱怨每次都要向框架拿MD5映射,直到框架一次BUG發生。
忽然一天框架發現一個全局性BUG,而且立刻作出了修復,業務團隊也立刻發佈上線,但這種事情出現第二次、第三次框架這邊便壓力大了,這個時候框架層面但願業務只須要引用一個不帶緩存的seed.js,seed.js要怎麼加載是他本身的事情:
<script type="text/javascript" src="seed.js"></script>
//seed.js須要按需加載的資源 <script src="libs_md5.js"></script> <script src="main_md5.js"></script>
固然,因爲js加載是順序是不可控的,咱們須要爲seed.js實現一個最簡單的順序加載模塊,映射什麼的由構建工具完成,每次作覆蓋發佈便可,這樣作的缺點是額外增長一個seed.js的文件,而且要承擔模塊加載代碼的下載量。
也會有團隊將靜態資源緩存至localstorage中,以期作離線應用,可是我通常用它存json數據,沒有作過靜態資源的存儲,想要嘗試的朋友必定要作好資源更新的策略,而後localstorage的讀寫也有必定損耗,不支持的狀況還須要作降級處理,這裏便很少介紹。
若是是Hybrid的話,狀況有所不一樣,須要將公共資源打包至native中,業務類就不要打包了,不然native會愈來愈大。
所謂工程化,能夠簡單認爲是將框架的職責拓寬再拓寬,主旨是幫業務團隊更好的完成需求,工程化會預測一些常碰到的問題,將之扼殺在搖籃,而這種路徑是可重用的,是具備可持續性的,好比第一個優化去除冗餘,是在屢次去除冗餘代碼,思考冗餘出現的緣由而最終思考得出的一個避免冗餘的方案,前端工程化須要考慮如下問題:
重複工做;如通用的流程控制機制,可擴展的UI組件、靈活的工具方法 重複優化;如下降框架層面升級帶給業務團隊的耗損、幫助業務在無感知狀況下作掉大部分優化(好比打包壓縮什麼的) 開發效率;如幫助業務團隊寫可維護的代碼、讓業務團隊方便的調試代碼(好比Hybrid調試)
要完成前端工程化,少不了工程化工具,requireJS與grunt的出現,改變了業界前端代碼的編寫習慣,同時他們也是推進前端工程化的一個基礎。
requireJS是一偉大的模塊加載器,他的出現讓javascript製做多人維護的大型項目變成了事實;grunt是一款javascript構建工具,主要完成壓縮、合併、圖片壓縮合並等一系列工做,後續又出了yeoman、Gulp、webpack等構建工具。
這裏這裏要記住一件事情,咱們的目的是完成前端工程化,不管什麼模塊加載器或者構建工具,都是爲了幫助咱們完成目的,工具不重要,目的與思想才重要,因此在完成工程化前討論哪一個加載器好,哪一種構建工具好是捨本逐末的。