陸續整理和不斷更新網路上給出的前端效能的優化方案。
這裡只是做一個總概括式的索引,每一個方案都十分值得推敲和細說。
1 請求和響應
快取控制
請求頭裡,可以傳送 If-Modified-Since 以及 If-None-Match 等資訊,來詢問服務端請求內容是否有更新,如果沒有更新,可返回304,告訴瀏覽器使用快取,避免重新下載資源。Pragma 和 Cache-Control 等也能控制快取。如告訴服務端不要快取等。
響應頭裡,Expires 可以告訴瀏覽器過期時間,Last-Modified 最近更新時間,ETag 則可允許瀏覽器進行快取驗證(在 If-None-Match 請求資訊中使用)。
複用TCP
請求頭裡,Connection 可控制 TCP 通道的使用,使用 keep-alive 可以複用上次開啟的 TCP。
GZIP壓縮
如果可以啟用 gzip 壓縮,將減少響應資料大小,加快響應。請求頭裡面可用 Accept-Encoding 告知瀏覽器支援的壓縮方式,而服務端則用 Content-Encoding 作為回應。
Cookies
傳送請求時,cookies 也在請求之中。因此,cookies 也可以作為減少請求的優化物件。如,根據同源限制策略,可以使用多個域名載入資源,如載入靜態資源,就不會傳送多餘的 cookies;同時,合理設定 cookies 的路徑和域名,如在子站避免不必要的來自父站的 cookies。
減少HTTP請求
有很多細節可以實現,比如CSS Sprites、Data URL等等,由於此部分內容和下述內容有所重複,故部分細節在下面會講到。
多域名分發
同域下瀏覽器能併發的請求有限,而為了增加併發,尤其是一些靜態資源上,可以使用多個域名。但由於域名DNS解析本身也是耗時的,所以實踐原則是2-4個為宜。
需要額外提醒的是,載入影像資源的時候,併發沒有問題;但在載入 JavaScript 指令碼的時候,還是會暫停載入其他資源。
使用CDN
根據使用者能訪問的最快位置加速訪問。
避免重定向和404
重定向和404將浪費載入請求。
favicon.ico
瀏覽器預設載入的資源,最好能夠快取之。
2 HTML
減少DOM
過多的DOM元素會影響渲染、載入、執行。除了精簡頁面結構外,還可以適時刪除不必要的DOM元素(頁面內已經不會再訪問的元素),又或者可以懶載入(不一定會使用到的元素,如登入框)。
CSS 和 JavaScript 檔案位置
CSS 放 head,JavaScript 放 body 閉標籤前。乃是因為:
- 樣式表不參與 DOM 修改,所以不會為了解析樣式停止文件解析
- 瀏覽器要避免重繪,在沒有拿到全部樣式前不會開始渲染
- 解析樣式時,有的瀏覽器(FF)會停止指令碼執行,而有的(Webkit)則會在指令碼訪問樣式屬性但可能受未載入樣式影響時停止指令碼執行
- 指令碼解析中可能請求樣式,如果樣式還未解析完畢就會出錯
- 指令碼執行將暫停文件的解析和資源的下載
因此,將二者放在適當的位置,能夠極大提高渲染效率。
指令碼延遲載入
可將指令碼新增 defer 和 async 屬性。兩個屬性的共通點在於,指令碼的載入和文件的解析是同步進行的,而區別在於:async 一旦載入完畢,立即停止文件解析並執行指令碼;defer 等待文件解析完畢後再執行。
合理使用內聯
指令碼和樣式,應按需選擇內聯或者外鏈。對於訪問少、樣式和指令碼複用少的頁面,可以考慮使用內聯樣式從而減少 HTTP 請求。但如果頁面訪問頻繁,樣式指令碼在多個頁面經常複用,使用外鏈則是最優選擇。
無論如何,需要避免使用 @import 來匯入樣式。
而影像也是一樣,高階瀏覽器支援將影像資料直接 base64 編碼在 src 屬性裡,必要時可直接在 HTML 裡輸出圖片資料。
減少iframe
iframe 本身有許多優點,比如可以並行下載指令碼,適合載入慢內容(如廣告),同時瀏覽器可以對其進行安全控制。
減少使用 iframe 的主要考慮是:iframe 會阻礙頁面載入,同時也沒有語義。
3 CSS
選擇器
選擇器效率排行如下:
- ID選擇器
- 類選擇器
- 標籤選擇器
- 相鄰選擇器
- 子選擇器
- 後代選擇器
- 萬用字元選擇器
- 屬性選擇器
- 偽類選擇器
效率與優先順序並不是對等關係,優先順序高的不一定效率高。如 #id.class 合用比 單個 #id 的優先順序高,但效率卻比值慢。
選擇器書寫建議是:
- 避免使用萬用字元
- 不使用標籤名或類名修飾ID規則:如果規則使用ID選擇器作為關鍵選擇器,不要給規則新增標籤名。因為ID本身就是唯一的,新增標籤名會不必要地降低匹配效率
- 不使用標籤名修飾類:相較於標籤,類更具獨特性
- 儘量選擇最具體的方式:造成低效的最簡單粗暴的原因就是在標籤上使用太多規則。給元素新增類可以更快細分到類方式,可以減少規則去匹配標籤的時間
- 關於後代選擇器和子選擇器:避免使用後代選擇器,非要用的話建議用子選擇器代替,但子選擇器也要慎用,標籤規則永遠不要包含子選擇器
- 利用可繼承性:沒必要在一般內容上宣告樣式
避免濾鏡、表示式、Hack
效率低。
Sprites
合併圖片可減少 HTTP 請求。其他建議有:
- Sprite 中水平排列圖片,垂直排列會增加檔案大小
- Sprite 中把顏色較近的組合在一起可以降低顏色數,理想狀況是低於256色以便適用PNG8格式
- 不要在Spirite的影像中間留有較大空隙。這雖然不大會增加檔案大小,但對於使用者代理來說它需要更少的記憶體來把圖片解壓為畫素地圖。100×100的圖片為1萬畫素,1000×1000就是100萬畫素
使用3D動畫
使用 transform: translate3d 等可加速 GPU 渲染。
4 JavaScript
避免重排
渲染中可能存在的高成本操作:
- 修改、增加、刪除DOM節點
- 移動DOM位置或者動畫效果
- CSS樣式修改(重繪比重排好些)
- 調整視窗大小,或者滾動時有絕對定位、fixed 背景以及動畫
- 修改頁面預設字型
瀏覽器一般會快取Render Tree的更新渲染,但以下情況除外:
- 調整視窗大小和修改頁面預設字型
- client/offset/scroll
- getComputedStyle() currentStyle
優化建議:
- 修改 className 而非 style
- 離線 DOM 後修改,如 documentFragment 或者 display:none 後再調整樣式
- 快取屬性值
- 動畫使用 absolute/fixed
- 不使用 table 佈局(牽一髮動全身)
- 修改層級比較低的 DOM
事件委託
將多個節點上的事件放到其父節點(經典案例:將 li 上的事件繫結到 ul 上)。
記憶體管理
合理釋放和快取記憶體。如快取複用的屬性,接觸物件引用等。
5 資源
壓縮大小
壓縮樣式、指令碼、影像等資源的大小。
針對影像資源,可從預覽小圖、格式選擇等多角度優化。
懶載入
如影像的懶載入(滾動到顯示區域後才載入)等。
預載入
針對之後會用到的資源提前載入。
6 客戶端
localStorage 快取
相比 cookies,localStorage 儲存容量更大。可以將一些靜態資源(如 jQuery庫)等快取。