前端性能優化總結
資源優化
緩存
最好的資源優化就是不加載資源。緩存也是最見效的優化手段。說實話,雖說客戶端緩存發生在瀏覽器端,但緩存主要仍是服務端來控制,與咱們前端關係並非很大。但仍是有必要了解一下。javascript
緩存包括服務端緩存和客戶端緩存,本文只談客戶端緩存。所謂客戶端緩存主要是http緩存。http緩存主要分爲強制緩存和協商緩存。css
強制緩存
- Expires(http1.0)
在http1.0中使用Expires來作強制緩存。Exprires的值爲服務端返回的數據到期時間。當再次請求時的請求時間小於返回的此時間,則直接使用緩存數據。但因爲服務端時間和客戶端時間可能有偏差,這也將致使緩存命中的偏差。html
- Cache-Control
Cache-Control有不少屬性,不一樣的屬性表明的意義也不一樣。前端
- private:客戶端能夠緩存
- public:客戶端和代理服務器均可以緩存
- max-age=t:緩存內容將在t秒後失效
- no-cache:須要使用協商緩存來驗證緩存數據
- no-store:全部內容都不會緩存。
協商緩存
瀏覽器第一次請求數據時,服務器會將緩存標識與數據一塊兒響應給客戶端,客戶端將它們備份至緩存中。再次請求時,客戶端會將緩存中的標識發送給服務器,服務器根據此標識判斷。若未失效,返回304狀態碼,瀏覽器拿到此狀態碼就能夠直接使用緩存數據了。java
- Last-Modified
服務器在響應請求時,會告訴瀏覽器資源的最後修改時間webpack
- if-Modified-Since
瀏覽器再次請求服務器的時候,請求頭會包含此字段,後面跟着在緩存中得到的Last-Modified(最後修改時間)。服務端收到此請求頭髮現有if-Modified-Since,則與被請求資源的最後修改時間進行對比,若是大於被請求資源最後修改時間則返回304,瀏覽器從緩存獲取資源。若是小於被請求資源最後修改時間,則返回200,並返回最新資源,瀏覽器從服務端獲取最新的資源,並緩存。css3
- Etag
由服務器生成的每一個資源的惟一標識字符串web
- If-None-Match
再次請求服務器時,瀏覽器的請求報文頭部會包含此字段,後面的值爲在緩存中獲取的標識。服務器接收到次報文後發現If-None-Match則與被請求資源的惟一標識進行對比。若是相同,說明資源沒有被修改過,返回304,瀏覽器從緩存獲取資源,若是不一樣說明資源被修改過,則返回200,並返回最新資源,瀏覽器從服務端獲取最新資源,並緩存。面試
Last-Modified與ETag是能夠一塊兒使用的,服務器會優先驗證ETag,一致的狀況下,纔會繼續比對Last-Modified,最後才決定是否返回304。
若是使用前端打包工具,能夠在打包文件時候在給文件添加版本號或者hash值,一樣能夠區分資源是否過時。chrome
減小http請求
- 使用CDN託管靜態資源
- 能夠藉助gulp、webpack等打包工具對js、css等文件合併與壓縮
- 圖片懶加載、按需加載,當滾動到圖片可視區域纔去加載圖片
- 小圖片而且基本不會改動的圖片使用base64編碼傳輸。base64不要濫用,即便小圖片通過base64編碼以後也會生成很長的字符串,若是濫用base64反而會拔苗助長
- 雪碧圖,這個也是針對基本不會更改的圖片才使用雪碧圖,由於若是一張圖片修改,會致使整個雪碧圖從新生成,若是亂用也會拔苗助長。
減少http請求資源體積
- 藉助webpack、gulp等工具壓縮資源
- 服務端開啓gzip壓縮(壓縮率很是可觀,通常都在30%之上)
- 若是有用打包工具,打包優化要作好,公共資源、提取第三方代碼、不須要打包的庫...
渲染優化
讀過前面js運行機制的應該知道,從瀏覽器輸入url,到頁面出如今屏幕上,都發生了哪些事(tcp握手、dns解析等不在認知範圍)。
- FPS 16ms 小於10ms完成最好 Google devtool 查看幀率
若是瀏覽器FPS到達60,就會顯得比較流暢,大多數顯示器的刷新頻率是60Hz,瀏覽器會自動按照這個頻率刷新動畫。
按照FPS等於60來計算,平均一幀的時間爲1000ms/60 = 16.7ms,因此每次渲染時間不能超過16ms,若是超過這個時間就會出現丟幀、卡頓現象。
能夠在chrome瀏覽器開發者工具中的Timeline中查看刷新率,能夠查看全部幀率耗時狀況以及某一幀的執行狀況。Timeline的使用教程:https://segmentfault.com/a/11...
爲了保證正常的FPS,有些渲染性能優化仍是有必要的。下面所介紹的都是有關渲染優化的策略。
- 儘可能使用css3來作動畫
總所周知,css的性能要比js快,因此能使用css,儘可能不用js來實現
- 避免使用setTimeout或setInterval,儘可能使用requestAnimationFrame來作動畫或者高頻Dom操做。
由於setTimeout和setInterval沒法保證callback函數的執行時機,極可能在幀結束的時候執行,從而致使丟幀,可是requestAnimationFrame能夠保證callback函數在每幀動畫開始的時候執行
requestAnimationFrame的中文MDN地址:https://developer.mozilla.org...
- 複雜的計算操做使用Web Workers
若是有須要複雜的數據操做,好比對一個有一個個元素的數組遍歷求和,那麼Web Workers在適合不過了。
Web Workers可讓JavaScript腳本運行在後臺線程(相似於建立一個子線程),然後臺線程不會影響到主線程中的頁面。不過,使用Web Workers建立的線程是不能操做DOM樹。
有關Web Workers的更多能夠查看MDN詳解:https://developer.mozilla.org...
- css放在頭部,js放在尾部。
讀過前面js運行機制的應該知道頁面渲染是怎樣一個過程,再也不贅述了。css放在頭部會避免生成html樹以後從新佈局的閃屏現象,js通常對頁面的影響較大,通常放在尾部最後執行。
- 事件防抖(debounce)與節流(throttle)
針對高頻觸發的事件(mousemove、scroll)等事件,若是不加以控制會在短期內觸發不少事件。
函數防抖是指頻繁觸發的狀況下,只有足夠的空閒時間,才執行代碼一次。場景:註冊時郵箱的輸入框,隨着用戶的輸入,實時判斷郵箱格式是否正確,當第一次輸入事件觸發,設置定時:在800ms以後執行檢查。假如只過了100ms,上次的定時還沒執行,此時清除定時,從新定時800ms。直到最近一次的輸入,後面沒有緊鄰的輸入了,這最近一次的輸入定時計時結束,終於執行了檢查代碼。
const filter = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/; $("#email").on("keyup",checkEmail()); function checkEmail(){ let timer=null; return function (){ clearTimeout(timer); timer=setTimeout(function(){ console.log('執行檢查'); },800); } }
函數節流是指必定時間內js方法只跑一次。就是原本一秒要執行100次的變成一秒執行10次。
場景:函數節流應用的實際場景,多數在監聽頁面元素滾動事件的時候會用到。
var canRun = true; document.getElementById("throttle").onscroll = function(){ if(!canRun){ // 判斷是否已空閒,若是在執行中,則直接return return; } canRun = false; setTimeout(function(){ console.log("函數節流"); canRun = true; }, 300); };
- Dom操做
前端開發人員都知道Do操做是很是耗時的(曾親測過30*30的表格遍歷添加樣式)。因此儘可能避免頻繁的Dom操做,若是避免不了就儘可能對DOm操做作優化。
1.:緩存Dom查詢,好比經過getElementByTagName('div')獲取Dom集,而不是逐個獲取。 2: 合併Dom操做,使用createDocumentFragment()
var frag = document.createDocumentFragment() for (i<10) { var li = document.createElement('li') frag.appendChild(li) } document.body.appendChild(frag)
3: 使用React、Vue等框架的虛擬dom(原理目前還不明白),能夠更快的實現dom操做。
- 儘可能避免重繪(rePaint)和迴流(reFlow)
若是使用js修改元素的顏色或者背景色就會觸發重繪,重繪的開銷仍是比較昂貴的,由於瀏覽器會在某一個DOM元素的視覺效果改變後去check這個DOM元素內的全部節點。
若是修改元素的尺寸和位置就會發生迴流,迴流開銷更大,它會在某一個DOM元素的位置發生改變後觸發,並且它會從新計算全部元素的位置和在頁面中的佔有的面積,這樣的話將會引發頁面某一個部分甚至整個頁面的從新渲染。
- css3硬件加速
瀏覽器渲染時,會分爲兩個圖層:普通圖層和複合圖層。
普通文檔流內能夠理解爲一個複合圖層,absolute、fixed佈局雖然能夠脫離普通文檔流,但它仍然屬於普通圖層,不會啓動硬件加速。上面說的重繪(rePaint)和迴流(reFlow)說的就是普通圖層上的重繪和迴流。
複合圖層會啓動硬件加速。和普通圖層不在同一個圖層,因此複合圖層不會影響普通圖層,若是一個元素被提高到複合圖層,再操做該元素時,就不會引發普通圖層的重繪和迴流,從而提高渲染性能。
如何啓動硬件加速:
1.使用translate3d和translateZ
webkit-transform: translateZ(0); -moz-transform: translateZ(0); -ms-transform: translateZ(0); -o-transform: translateZ(0); transform: translateZ(0); webkit-transform: translate3d(0,0,0); -moz-transform: translate3d(0,0,0); -ms-transform: translate3d(0,0,0); -o-transform: translate3d(0,0,0); transform: translate3d(0,0,0);
2.使用opacity
須要動畫執行的過程當中纔會建立合成層,動畫沒有開始或結束後元素還會回到以前的狀態
3.使用will-chang屬性
這個屬性比較不經常使用,通常配合opacity與translate使用
針對webkit瀏覽器,啓用硬件加速有些時候可能會致使瀏覽器頻繁閃爍或抖動,可使用下面方法消除:
-webkit-backface-visibility:hidden; -webkit-perspective:1000;
若是使用硬件加速,請使用z-index配合使用, 由於若是這個元素添加了硬件加速,而且index層級比較低, 那麼在這個元素的後面其它元素(層級比這個元素高的,或者相同的,而且releative或absolute屬性相同的), 會默認變爲複合層渲染,若是處理不當會極大的影響性能
- 避免強制同步佈局和佈局抖動
瀏覽器渲染過程爲:js/css(javascript) > 計算樣式(style) > 佈局(layout) > 繪製(paint) > 渲染合併圖層(Composite)
JavaScript:JavaScript實現動畫效果,DOM元素操做等。
Style(計算樣式):肯定每一個DOM元素應該應用什麼CSS規則。
Layout(佈局):計算每一個DOM元素在最終屏幕上顯示的大小和位置。
Paint(繪製):在多個層上繪製DOM元素的的文字、顏色、圖像、邊框和陰影等。
Composite(渲染層合併):按照合理的順序合併圖層而後顯示到屏幕上。
在js中若是讀取style屬性的某些值就會讓瀏覽器強行進行一次佈局、計算,而後再返回值,好比:
offsetTop, offsetLeft, offsetWidth, offsetHeight scrollTop/Left/Width/Height clientTop/Left/Width/Height width,height 請求了getComputedStyle(), 或者 IE的 currentStyle
因此,若是強制瀏覽器在執行JavaScript腳本以前先執行佈局過程,這就是所謂的強制同步佈局。
好比下面代碼:
requestAnimationFrame(logBoxHeight); // 先寫後讀,觸發強制佈局 function logBoxHeight() { // 更新box樣式 box.classList.add('super-big'); // 爲了返回box的offersetHeight值 // 瀏覽器必須先應用屬性修改,接着執行佈局過程 console.log(box.offsetHeight); } // 先讀後寫,避免強制佈局 function logBoxHeight() { // 獲取box.offsetHeight console.log(box.offsetHeight); // 更新box樣式 box.classList.add('super-big'); }
在JavaScript腳本運行的時候,它能獲取到的元素樣式屬性值都是上一幀畫面的,都是舊的值。所以,若是你在當前幀獲取屬性以前又對元素節點有改動,那就會致使瀏覽器必須先應用屬性修改,結果執行佈局過程,最後再執行JavaScript邏輯。
若是連續屢次強制同步佈局,就會致使佈局抖動
好比下面代碼:
function resizeAllParagraphsToMatchBlockWidth() { for (var i = 0; i < paragraphs.length; i++) { paragraphs[i].style.width = box.offsetWidth + 'px'; } } 做者:SylvanasSun 連接:https://juejin.im/post/59da456951882525ed2b706d 來源:掘金 著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
咱們知道瀏覽器是一幀一幀的刷新頁面的,對於每一幀,上一幀的佈局信息都是已知的。
強制佈局就是使用js強制瀏覽器提早佈局,好比下面代碼:
// bed 每次循環都要去獲取left ,就會發生一次迴流 function logBoxHeight() { box.style.left += 10 console.log(box.style.left) } // goog var width = box.offsetWidth; function resizeAllParagraphsToMatchBlockWidth() { for (var i = 0; i < paragraphs.length; i++) { // Now write. paragraphs[i].style.width = width + 'px'; } }
- DOMContentLoaded與Load
DOMContentLoaded 事件觸發時,僅當DOM加載完成才觸發DOMContentLoaded,此時樣式表,圖片,外部引入資源都還沒加載。而load是等全部的資源加載完畢纔會觸發。
1. 解析HTML結構。 2. 加載外部腳本和樣式表文件。 3. 解析並執行腳本代碼。 4. DOM樹構建完成。//DOMContentLoaded 5. 加載圖片等外部文件。 頁面加載完畢。//load
- 視覺優化
等待加載時間能夠合理使用loading gif動圖必定程度上消除用戶等待時間的煩躁感
代碼性能
代碼對性能的影響可大可小,可是養成一個良好的寫代碼習慣和高質量的代碼,會潛移默化的提升性能,同時也能提升本身的水平。廢話很少說,直接看我總結的部分要點(由於這一部分知識點太多,須要你們寫代碼的時候多多總結)。
- 避免全局查找
訪問局部變量會比訪問全局變量快,由於js查找變量的時候如今局部做用局查找,找不到在逐級向上找。
// bad function f () { for (...){ console.log(window.location.href) } } //good function f () { var href = window.location.href for (...){ console.log(href) } }
- 循環技巧
// bed for(var i = 0; i < array.length; i++){ .... } // good for(var i = 0, len = array.length; i < len; i++){ .... } // 不用每次查詢長度
- 不要使用for in 遍歷數組
for in是最慢的,其餘的都差很少,其中直接使用for循環是最快的。for in只是適合用來遍歷對象。
- 使用+''代替String()吧變量轉化爲字符串
var a = 12 //bad a = String(a) // good var a = 12 a = a + ''
這個還有不少相似的,好比使用*1代替parseInt()等都是利用js的弱類型,其實這樣對性能提高不是很大,網上有人測試過,進行十幾萬次變量轉換,才快了零點幾秒。
- 刪除dom
刪除dom元素要刪除註冊在該節點上的事件,不然就會產生沒法回收的內存,在選擇removeChild和innerHTML=''兩者之間儘可能選擇後者,聽說removeChild有時候沒法有效的釋放節點(具體緣由不明)
- 使用事件代理處理事件
任何能夠冒泡的事件均可以在節點的祖先節點上處理,這樣對於子節點須要綁定相同事件的狀況就不用分別給每一個子節點添加事件監聽,而是都提高到祖先節點處理。
- 經過js生成的dom對象必須append到頁面中
在IE下,js建立的額dom若是沒有添加到頁面,這部份內存是不會被回收的
- 避免與null比較
可使用下面方法替換與null比較
1.若是該值爲引用類型,則使用instanceof檢查其構造函數
2.若是該值爲基本類型,使用typeof檢查類型
- 儘可能使用三目運算符代替if else
if(a>b){num = a} else{num = b} // 能夠替換爲 num = a > b ? a : b
- 當判斷條件大於3中狀況時,使用switch代替if
由於switch的執行速度比if要快,也別是在IE下,速度大約是if的兩倍
先總結這麼多,其實性能優化還有不少,像預加載、服務端渲染、css選擇器優化等等。等有機會再總結