連結:前端優化
前端優化
瀏覽器傳送HTTP請求,伺服器收到請求全文後,返回HTTP響應,在瀏覽器接收之後結束這個過程。瀏覽器和伺服器只有一次互動的機會,瀏覽器主動發起,而伺服器被動地根據收到的請求內容返回結果。一個完整的請求都需要經過DNS定址、與伺服器建立連線、傳送資料、等待伺服器響應、接收資料的過程。
前端優化的途徑
- 頁面級別的優化,例如HTTP請求數、指令碼的無阻塞載入、內聯指令碼的位置優化等;
- 程式碼級別的優化,例如JavaScript中的DOM操作優化、CSS選擇符優化、圖片優化以及HTML結構優化等。
頁面級優化
1. 減少HTTP請求數
減少HTTP請求數的主要途徑
1、從設計實現層面簡化頁面
保持頁面簡潔、減少資源的使用是最直接的。能使用CSS替代效果就儘量少使用圖片。
2、合理設定HTTP快取
恰當地快取設定可以大大減少HTTP請求。被快取資源的請求伺服器是304響應,只有Header
沒有body
,沒有節省頻寬。對於多個頁面都可能使用到的程式碼,儘量拆分到同一個檔案中。如果是嵌入頁面換來的是增大了頁面的體積,而且無法利用瀏覽器快取。
3、資源合併和壓縮
如果可以,儘可能將外部的指令碼、樣式進行合併,多個合為一個。另外,CSS、JavaScript、image都可以用相應的工具進行壓縮。
4、CSS Sprites
合併CSS圖片,減少請求數的有一個好辦法。
5、lazy load image
這個策略實際上並不一定能減少HTTP請求數,但是卻能在某些條件下或者頁面剛載入時減少HTTP請求數。對於圖片而言,在頁面剛載入時可以只載入第一屏,當使用者繼續往後滾屏時才載入後續的圖片。以前的做法是在載入時把第一屏之後的圖片地址快取在textarea
標籤中,待使用者往下滾屏時才惰性載入。百度圖片和花瓣網也是用這種流行的瀑布流載入圖片。
2. 將外部指令碼置底
外鏈指令碼在載入時會阻塞其他資源,例如在指令碼載入完成之前,它後面的圖片、樣式以及其他指令碼都處於阻塞狀態,直到指令碼載入完成後才會開始載入。如果把指令碼放在比較靠前的位置,則會影響整個頁面的載入速度從而影響使用者體驗。最簡單可依賴的方法是將指令碼儘可能往後挪,減少對併發下載的影響。如果時效性允許的話,可以考慮在DOMLoaded
事件觸發時載入,或者用setTimeout
方式來靈活控制載入的時機。
3. 非同步執行inline
指令碼
inline
指令碼對效能的影響比外部指令碼大很多。首先,與外部指令碼一樣,inline
指令碼在執行時也會阻塞併發請求,除此之外,由於瀏覽器在頁面處理方面時單執行緒的,當inline
指令碼在頁面渲染之前執行時,頁面的渲染工作則會被推遲。簡而言之,inline
指令碼在執行時頁面處於空白狀態。
鑑於以上兩點,建議將執行時間較長的inline
指令碼非同步執行。非同步執行的方式有很多種,例如使用script
元素的defer
屬性、使用setTimeout
,此外,在HTML5中引入了web workers
的機制,恰恰可以解決此類問題。
4. lazy load JavaScript
目前的做法大概有兩種,一種是為流量特別大的頁面專門定製一個專用的mini
版框架,另一種則是lazy load
,最初只載入核心模組,其他模組可以等到需要使用的時候才載入,類似於java
的swing
,引入需要的元件庫檔案。
5. 將CSS放在head
中
6. 減少不必要的HTTP跳轉
對於以目錄形式訪問的HTTP連結,很多人都會忽略連結最後是否帶/
,假如伺服器對此區別對待,那麼其中很可能隱藏了301跳轉,增加了多餘請求。
程式碼級優化
1. JavaScript
1、DOM
DOM操作應該是指令碼中最耗效能的一類操作,例如增、刪、查、改DOM元素或者對DOM集合進行操作。如果指令碼中包含了大量的DOM操作則需要注意html collection
。
在指令碼中document.images
、document.forms
、getElementsByTagName()
返回的都是HTMLCollection
型別的集合,在平時使用的時候大多將它作為陣列來使用,因為它有length
屬性,也可以使用索引訪問每一個元素。不過在訪問效能上則比陣列要差很多,原因這個集合並不是一個靜態的結果,它表示的僅僅是一個特定的查詢,每次訪問該集合時都會重新執行這個查詢從而更新查詢結果。所謂的訪問集合包括讀取集合的length
屬性、訪問集合中的元素。
因此,當你需要遍歷HTML collection
時,儘量將它轉為陣列後再訪問,以提高效能。即使不轉換為陣列,也請儘可能少地訪問它,例如在遍歷的時候可以將length
屬性、成員儲存到區域性變數後再使用區域性變數。
2、慎用with
with(obj){p=1};
程式碼塊的行為實際上是修改了程式碼塊中的執行環境,將obj
放在了其作用於的最前端,在with
程式碼塊中訪問非區域性變數都是先從obj
上開始查詢,如果沒有再依次按作用域鏈向上查詢,因此使用with
相當於增加了作用域鏈長度。而每次查詢作用域鏈都是要消耗時間的,過長的作用域鏈會導致查詢效能下降。
因此,除非你能肯定在with
程式碼中只放obj
中的屬性,否則慎用with
,替代的可以使用區域性變數快取需要訪問的屬性。
3、避免使用eval
和Function
每次eval
或Function
建構函式作用於字串表示的原始碼時,指令碼引擎都需要將原始碼轉換為可執行程式碼。這是很消耗資源的操作——通常比簡單的函式呼叫慢100倍以上。
eval
函式效率特別低,由於事先無法知曉傳給eval
的字串中的內容,eval
在其上下文中解析要處理的程式碼,也就是說編譯器無法優化上下文,因此只能有瀏覽器在執行時解析程式碼,這對效能影響很大。
Function
建構函式比eval
略好,因為使用此程式碼不會影響周圍程式碼,但其速度仍很慢。
此外,使用eval
和Function
不利於JavaScript壓縮工具執行壓縮。
4、減少作用域鏈查詢
作用域鏈查詢問題,這一點在迴圈中尤其需要注意。如果在迴圈中需要訪問非本作用域下的變數時請在遍歷之前用區域性變數快取該變數,並在遍歷結束後再重複那個變數,這一點對全域性變數尤其重要,因為全域性變數處於作用域鏈的最頂端,訪問時的查詢次數是最多的。
此外,要減少作用域鏈查詢還應該減少閉包的使用。閉包的變數可能儲存到記憶體中,記憶體消耗很大,解決方法是在退出函式前,將不使用的區域性變數刪除。
5、資料訪問
JavaScript中的資料訪問包括直接量(字串、正規表示式)、變數、物件屬性以及陣列,其中對直接量和區域性變數的訪問是最快的,對物件屬性以及陣列的訪問需要更大的開銷。當出現以下情況時,建議將資料放入區域性變數:
- 對任何物件屬性的訪問超過1次
- 對任何陣列成員的訪問次數超過1次
另外,還應當儘可能的減少對物件以及陣列深度查詢。
6、字串拼接
在JavaScript中使用+
號來拼接字串效率是比較低的,因為每次執行都會開闢新的記憶體並生成新的字串變數,然後拼接結果賦值給新變數。之前使用jQuery+Ajax互動頁面,很多時候都是將後臺傳輸過來的資料和前端HTML
結構拼接成字串,然後呈現在頁面HTML容器裡。
與之相比更為高效的做法是使用陣列的join
方法,即將需要拼接的字串放在陣列中最後呼叫其join
方法得到結果。不過由於使用陣列也有一定的開銷,因此當需要拼接的字串較多時可以考慮使用此方法。
2. CSS選擇符
在大多數人的觀念中,都覺得瀏覽器對CSS選擇符的解析是從左往右進行的。
如果是從右往左解析則效率會很高,因為第一個ID選擇基本上就把查詢的範圍限定了,但實際上瀏覽器對選擇符的解析是從右往左進行的。#tag A {color: "#ccc";}
,瀏覽器必須遍歷查詢每一個A
標籤的祖先節點,效率並不像之前想象的那麼高。根據瀏覽器的這一行為特點,在寫選擇符的時候需要注意很多事項。