前端是龐大的,包括 HTML、 CSS、 Javascript、Image 、Flash等等各種各樣的資源。前端優化是複雜的,針對各個面向的資源都有不同的方式。那麼,前端優化的目的是什麼 ?
1. 從使用者角度而言,優化能讓頁面載入得更快、對使用者的操作回應得更及時,能夠提供使用者更友善的體驗。
2. 從服務商角度而言,最佳化能夠減少頁面請求數、或減少請求所佔頻寬,能夠節省可觀的資源。
總之,恰當的最佳化不僅能夠改善網站的使用者體驗並且能夠節省相當的資源利用。
前端優化的途徑有很多,按粒度大致可以分為兩類,第一類是頁面級別的優化,例如HTTP請求數、腳本的無阻塞加載、內聯腳本的位置優化等;第二類則是程式碼層級的最佳化,例如Javascript中的DOM 操作優化、CSS選擇符優化、圖片優化以及HTML結構優化等等。另外,本著提高投入產出比的目的,後文提到的各種最佳化策略大致依照投入產出比從大到小的順序排列。
一、頁面級優化
1. 減少 HTTP請求數
這條策略基本上所有前端人都知道,而且也是最重要最有效的。都說要減少 HTTP請求,那請求多了到底會怎麼樣呢 ?首先,每個請求都是有成本的,既包含時間成本也包含資源成本。一個完整的請求都需要經過 DNS尋址、與伺服器建立連線、發送資料、等待伺服器回應、接收資料這樣一個 「漫長」 而複雜的過程。時間成本就是使用者需要看到或 「感受」 到這個資源是必須要等待這個過程結束的,資源上由於每個請求都需要攜帶數據,因此每個請求都需要佔用頻寬。另外,由於瀏覽器進行並發請求的請求數是有上限的(具體參見此處),因此請求數多了以後,瀏覽器需要分批進行請求,因此會增加用戶的等待時間,會給用戶造成站點速度慢這樣一個印象,即使可能使用者能看到的第一螢幕的資源都已經被要求完了,但是瀏覽器的進度條會一直存在。
減少 HTTP請求數的主要途徑包括:
(1). 從設計實現層面簡化頁面
如果你的頁面像百度首頁一樣簡單,那麼接下來的規則基本上都用不著了。保持頁面簡潔、減少資源的使用時最直接的。如果不是這樣,你的頁面需要華麗的皮膚,則繼續閱讀下面的內容。
(2). 合理設定 HTTP快取
快取的力量是強大的,恰當的快取設定可以大大的減少 HTTP請求。以有啊首頁為例,當瀏覽器沒有快取的時候訪問一共會發出78個請求,共600多K數據(如圖1.1),而當第二次訪問即瀏覽器已緩存之後訪問則僅有10個請求,共20多K資料(如圖1.2)。 (這裡要說明的是,如果直接F5刷新頁面的話效果是不一樣的,這種情況下請求數還是一樣,不過被緩存資源的請求伺服器是304響應,只有Header沒有Body ,可以節省頻寬)
怎麼才算合理設定?原則很簡單,能緩存越多越好,能緩存越久越好。例如,很少變化的圖片資源可以直接透過 HTTP Header中的Expires設定一個很長的過期頭 ;變化不頻繁而又可能會變的資源可以使用 Last-Modifed來做請求驗證。盡可能的讓資源能夠在快取中待得更久。關於HTTP快取的具體設定和原理此處就不再詳述了,有興趣的可以參考下列文章:
HTTP1.1協定中關於快取策略的描述
Fiddler HTTP Performance中關於快取的介紹
(3). 資源合併與壓縮
如果可以的話,盡可能的將外部的腳本、樣式進行合併,多個合為一個。另外, CSS、 Javascript、Image 都可以用對應的工具來壓縮,壓縮後往往能省下不少空間。
(4). CSS Sprites
合併 CSS圖片,減少請求數的另一個好方法。
(5). Inline Images
使用 data:
URL scheme的方式將圖片嵌入到頁面或 CSS中,如果不考慮資源管理上的問題的話,不失為一個好方法。如果是嵌入頁面的話換來的是增加了頁面的體積,而且無法利用瀏覽器快取。使用在 CSS中的圖片則更為理想一些。
(6). Lazy Load Images(自己對這一塊的內容還是不了解)
這條策略其實不一定能減少 HTTP請求數,但是卻能在某些條件下或頁面剛載入時減少 HTTP請求數。對於圖片而言,在頁面剛加載的時候可以只加載第一屏,當用戶繼續往後滾屏的時候才加載後續的圖片。這樣一來,如果用戶只對第一屏的內容感興趣時,那剩餘的圖片請求就都節省了。
有啊首頁
曾經的做法是在載入的時候把第一屏之後的圖片地址緩存在 Textarea標籤中,待用戶往下滾屏的時候才 “惰性” 加載。
2. 將外部腳本置底(將腳本內容在頁面資訊內容載入後再載入)
前文有談到,瀏覽器是可以並發請求的,這一特點使得其能夠更快的載入資源,然而外鏈腳本在載入時卻會阻塞其他資源,例如在腳本載入完成之前,它後面的圖片、樣式以及其他腳本都處於阻塞狀態,直到腳本載入完成後才會開始載入。如果將腳本放在比較前面的位置,則會影響整個頁面的載入速度從而影響使用者體驗。解決這一問題的方法有很多,在
這裡有比較詳細的介紹
(這裡是譯文和
更詳細的例子
),而最簡單可依賴的方法就是將腳本盡可能的往後挪,減少對並發下載的影響。
3. 異步執行 inline腳本(其實原理和上面是一樣,保證腳本在頁面內容後面加載。)
inline腳本對性能的影響與外部腳本相比,是有過之而無不及。首頁,與外部腳本一樣, inline腳本在執行的時候一樣會阻塞並發請求,除此之外,由於瀏覽器在頁面處理方面是單線程的,當inline腳本在頁面渲染之前執行時,頁面的渲染工作則會被推遲。簡而言之, inline腳本在執行的時候,頁面處於空白狀態。鑑於以上兩點原因,建議將執行時間較長的inline腳本異步執行,異步的方式有很多種,例如使用script元素的defer 屬性(存在兼容性問題和其他一些問題,例如不能使用document.write)、使用setTimeout
,此外,在HTML5中引入了
Web Workers的機制,恰恰可以解決這類問題。
4. Lazy Load Javascript(只有在需要加載的時候加載,在一般情況下並不加載信息內容。)
隨著Javascript框架的流行,越來越多的站點也使用起了框架。不過,一個框架往往包括了很多的功能實現,這些功能並不是每一個頁面都需要的,如果下載了不需要的腳本則算得上是一種資源浪費-既浪費了頻寬又浪費了執行花費的時間。目前的做法大概有兩種,一種是為那些流量特別大的頁面專門訂製一個專用的 mini版框架,另一種則是 Lazy Load。 YUI 則使用了第二種方式,在 YUI的實作中,最初只載入核心模組,其他模組可以等到需要使用的時候才載入。
5. 將CSS放在HEAD中
如果將CSS放在其他地方例如BODY中,則瀏覽器有可能還未下載和解析到CSS就已經開始渲染頁面了,這就導致頁面由無CSS狀態跳到CSS狀態,使用者體驗比較糟糕。除此之外,有些瀏覽器會在 CSS下載完成後才開始渲染頁面,如果 CSS放在靠下的位置就會導致瀏覽器將渲染時間延後。
6. 非同步請求 Callback(就是將一些行為樣式提取出來,慢慢的載入資訊的內容)
在某些頁面中可能存在這樣一種需求,需要使用 script標籤來異步的請求資料。類似:
Javascript:
/*Callback 函數*/
function myCallback(info){
//do something here
}
HTML:
cb返回的內容:
myCallback('Hello world!');
像以上這種方式直接在頁面上寫