雅虎前端優化的35條軍規。
內容部分
1. 儘量減少HTTP請求數
80% 的終端用戶響應時間都花在了前端上,其中大部分時間都在下載頁面上的各種組件:圖片,樣式表,腳本,Flash等等。減少組件數必然能夠減少頁面提交的HTTP請求數。 這是讓頁面更快的關鍵。
合併文件 是通過把所有腳本放在一個文件中的方式來減少請求數的,當然,也可以合併所有的 CSS。
CSS Sprites 是減少圖片請求數量的首選方式。把背景圖片都整合到一張圖片中,然後用 CSS 的 background-image
和 background-position
屬性來定位要顯示的部分。
圖像映射 可以把多張圖片合併成單張圖片,總大小是一樣的,但減少了請求數並加速了頁面加載。圖片映射只有在圖像在頁面中連續的時候纔有用,比如導航條。
行內圖片(Base64編碼) 用 data: URL
模式來把圖片嵌入頁面。
2. 減少DNS查找
在 DNS 查找完成之前,瀏覽器無法從主機名下載任何東西。
DNS 查找被緩存起來更高效,由用戶的 ISP(網絡服務提供商)或者本地網絡存在一個特殊的緩存服務器上,但還可以緩存在個人用戶的計算機上。只要瀏覽器在自己的 cache 裏還保留着這條記錄,它就不會向操作系統查詢 DNS。
如果客戶端的 DNS cache 是空的(包括瀏覽器的和操作系統的),DNS 查找數等於頁面上不同的主機名數,包括頁面URL,圖片,腳本文件,樣式表,Flash 對象等等組件中的主機名,減少不同的主機名就可以減少 DNS 查找。
把組件分散在 2 到 4 個主機名下,這是同時減少 DNS 查找和允許高併發下載的折中方案。
3. 避免重定向
重定向用 301 和 302 狀態碼,下面是一個有 301 狀態碼的 HTTP 頭:
HTTP/1.1 301 Moved Permanently
Location: http://example.com/newuri
Content-Type: text/html
瀏覽器會自動跳轉到 Location 域指明的 URL。重定向需要的所有信息都在 HTTP 頭部,而響應體一般是空的。其實額外的 HTTP 頭,比如 Expires 和 Cache-Control 也表示重定向。除此之外還有別的跳轉方式:refresh 元標籤和 JavaScript,但 如果你必須得做重定向,最好用標準的 3xxHTTP 狀態碼,主要是爲了讓返回按鈕能正常使用。
有一種常見的 極其浪費資源 的重定向,而且 web 開發人員一般都意識不到這一點,就是URL 尾部缺少一個斜線的時候。
一種替代方案是用 Alias 和 mod_rewrite
,前提是兩個代碼路徑都在相同的服務器上。如果是因爲域名變化而使用了重定向,就可以創建一條 CNAME(創建一個指向另一個域名的 DNS 記錄作爲別名)結合 Alias 或者 mod_rewrite 指令。
4. 讓Ajax可緩存
Ajax 的一個好處是可以給用戶提供即時反饋,因爲它能夠從後臺服務器異步請求信息。
最重要的提高 Ajax 性能的方法就是 讓響應變得可緩存,下面適用於 Ajax 的其它規則:
- Gzip 組件
- 減少 DNS 查找
- 壓縮 JavaScript
- 避免重定向
- 配置 ETags
5. 延遲加載組件
可以湊近看看頁面並問自己:什麼纔是一開始渲染頁面所必須的?其餘內容都可以等會兒。
工具可幫你減輕工作量:YUI Image Loader
可以延遲加載摺疊的圖片,還有 YUI Get utility
是一種引入 JS 和 CSS 的簡單方法。
最好讓性能目標符合其它 web 開發最佳實踐,比如 “漸進增強” 。如果客戶端支持 JavaScript,可以提高用戶體驗,但必須確保頁面在不支持 JavaScript 時也能正常工作。所以,在確定頁面運行正常之後,可以用一些延遲加載腳本增強它,以支持一些拖放和動畫之類的華麗效果。
6. 預加載組件
預加載的類型:
-
無條件預加載 —— 儘快開始加載,獲取一些額外的組件。
google.com
就是一個 sprite 圖片預加載的好例子,這個 sprite 圖片並不是 google.com 主頁需要的,而是搜索結果頁面上的內容。 -
條件性預加載 —— 根據用戶操作猜測用戶將要跳轉到哪裏並據此預加載。
在
search.yahoo.com
的輸入框裏鍵入內容後,可以看到那些額外組件是怎樣請求加載的。 -
提前預加載 —— 在推出新設計之前預加載。
可以通過在將要推出新設計之前預加載一些組件來減輕這種負面影響,老站可以利用瀏覽器空閒的時間來請求那些新站需要的圖片和腳本。
7. 減少DOM元素的數量
一個複雜的頁面意味着要下載更多的字節,而且用 JavaScript 訪問 DOM 也會更慢。
或許應該 用更好的語義化標記。
DOM元素的數量很容易測試,只需要在 Firebug 的控制檯裏輸入:
document.getElementsByTagName('*').length
那麼多少 DOM 元素纔算是太多呢?可以參考其它類似的標記良好的頁面,例如 Yahoo! 主頁是一個相當繁忙的頁面,但只有不到 700
個元素(HTML標籤)。
8. 跨域分離組件
分離組件可以最大化並行下載,但要確保只用不超過 2 - 4
個域,因爲存在 DNS 查找的代價。
例如,可以把 HTML 和動態內容部署在 www.example.org,而把靜態組件分離到 static1.example.org 和 static2.example.org。
9. 儘量少用iframe
<iframe>
的優點:
- 引入緩慢的第三方內容,比如標誌和廣告
- 安全沙箱
- 並行下載腳本
<iframe>
的缺點:
- 代價高昂,即使是空白的 iframe
- 阻塞頁面加載
- 非語義
10. 杜絕404
HTTP 請求代價高昂,完全沒有必要用一個 HTTP 請求去獲取一個無用的響應(比如404 Not Found),只會拖慢用戶體驗而沒有任何好處。
有些站點用的是有幫助的 404——“你的意思是xxx?”,這樣做有利於用戶體驗,但也浪費了服務器資源(比如數據庫等等)。
css部分
11. 避免使用CSS表達式
用 CSS 表達式動態設置 CSS 屬性,是一種強大又危險的方式。從 IE5 開始支持,但從 IE8 起就不推薦使用了。
12. 選擇<link>捨棄@import
前面提到了一個最佳實踐:爲了實現逐步渲染,CSS 應該放在頂部。
在 IE 中用 @import
與在底部用 <link>
效果一樣,所以最好不要用它。
13. 避免使用濾鏡
IE 專有的 AlphaImageLoader
濾鏡可以用來修復 IE7 之前的版本中半透明 PNG 圖片的問題。在圖片加載過程中,這個濾鏡會阻塞渲染,卡住瀏覽器,還會增加內存消耗而且是被應用到每個元素的,而不是每個圖片,所以會存在一大堆問題。
14. 把樣式表放在頂部
把樣式表放到文檔的 HEAD
部分能讓頁面看起來加載地更快。
js部分
15. 去除重複腳本
重複腳本會創建不必要的 HTTP 請求,執行無用的 JavaScript 代碼,而影響頁面性能。
避免不小心把相同腳本引入兩次的一種方法就是在模版系統中 實現腳本管理模塊。典型的 腳本引入方法 就是在 HTML 頁面中用 SCRIPT 標籤:
<script type="text/javascript" src="menu_1.0.17.js"></script>
16. 儘量減少DOM訪問
用 JavaScript 訪問 DOM 元素是很慢的,所以,爲了讓頁面反應更迅速,應該:
- 緩存已訪問過的元素的索引
- 先“離線”更新節點,再把它們添到 DOM 樹上
- 避免用 JavaScript 修復佈局問題
17. 用智能的事件處理器
有時候感覺頁面反映不夠靈敏,是因爲有太多頻繁執行的事件處理器被添加到了 DOM 樹的不同元素上,這就是推薦 使用事件委託 的原因。
如果一個 div 裏面有 10 個按鈕,應該只給 div 容器添加一個事件處理器,而不是給每個按鈕都添加一個。
事件能夠冒泡,所以可以捕獲事件並得知哪個按鈕是事件源。
18. 把腳本放在底部
腳本會阻塞並行下載,HTTP/1.1 官方文檔建議 瀏覽器每個主機名下並行下載的組件數不要超過兩個,如果圖片來自多個主機名,並行下載的數量就可以超過兩個。 如果腳本正在下載,瀏覽器就不開始任何其它下載任務,即使是在不同主機名下的。
有時候,並不容易把腳本移動到底部。舉個例子,如果腳本是用 document.write 插入到頁面內容中的,就沒辦法再往下移了。還可能存在作用域問題,在多數情況下,這些問題都是可以解決的。
一個常見的建議是用 推遲(deferred)腳本 ,有 DEFER
屬性的腳本意味着不能含有 document.write,並且提示瀏覽器告訴他們可以繼續渲染。
javascript, css
19. 把JavaScript和CSS放到外面
應該把 JavaScript 和 CSS 放到外部文件中還是直接寫在頁面裏?
實際上,用外部文件可以讓頁面更快 ,因爲 JavaScript 和 CSS 文件會被緩存在瀏覽器。
HTML 文檔中的行內 JavaScript 和 CSS 在每次請求該 HTML 文檔的時候都會重新下載。這樣做減少了所需的 HTTP 請求數,但增加了 HTML 文檔的大小。另一方面,如果 JavaScript 和 CSS 在外部文件中,並且已經被瀏覽器緩存起來了,那麼我們就成功地把 HTML 文檔變小了,而且還沒有增加 HTTP 請求數。
20. 壓縮JavaScript和CSS
壓縮 具體來說就是從代碼中去除不必要的字符以減少大小,從而提升加載速度。代碼最小化 就是去掉所有註釋和不必要的空白字符(空格,換行和tab)。在 JavaScript 中這樣做能夠提高響應性能,因爲要下載的文件變小了。兩個最常用的 JavaScript 代碼壓縮工具是 JSMin
和 YUI Compressor
,YUI compressor還可以壓縮CSS。
混淆 是一種可選的源碼優化措施,要比壓縮更復雜。
圖片
21. 優化圖片
嘗試把 GIF 格式轉換成 PNG
格式,看看是否節省空間。
在所有的 PNG 圖片上運行 pngcrush
(或者其它 PNG 優化工具)。
22. 優化CSS Sprite
在 Sprite 圖片中 橫向排列 一般都比縱向排列的最終文件小 。
組合 Sprite 圖片中的 相似顏色可以保持低色數 ,最理想的是 256 色以下 PNG8 格式。
“對移動端友好”,不要在 Sprite 圖片中留下太大的空隙。 雖然不會在很大程度上影響圖片文件的大小,但這樣做可以節省用戶代理把圖片解壓成像素映射時消耗的內存。
23. 不要用HTML縮放圖片
不要因爲在 HTML 中可以設置寬高而使用本不需要的大圖。如果需要
<img width="100" height="100" src="mycat.jpg" alt="My Cat" />
那麼圖片本身(mycat.jpg)應該是 100x100px 的,而不是去縮小 500x500px 的圖片。
24. 用小的可緩存的favicon.ico
爲了緩解 favicon.ico 的缺點,應該確保:
- 足夠小,最好在 1K 以下
- 設置合適的有效期 HTTP 頭(以後如果想換的話就不能重命名了),把有效期設置爲幾個月後一般比較安全,可以通過檢查當前 favicon.ico 的最後修改日期來確保變更能讓瀏覽器知道。
cookie
25. 給Cookie減肥
- 清除不必要的 cookie。
- 保證 cookie 儘可能小,以最小化對用戶響應時間的影響。
- 注意給 cookie 設置合適的域級別,以免影響其它子域。
- 設置合適的有效期,更早的有效期或者 none 可以更快的刪除 cookie,提高用戶響應時間。
26. 把組件放在不含cookie的域下
當瀏覽器發送對靜態圖像的請求時,cookie 也會一起發送,而服務器根本不需要這些 cookie。所以它們只會造成沒有意義的網絡通信量,應該確保對靜態組件的請求不含cookie。可以創建一個子域,把所有的靜態組件都部署在那兒。
移動端
27. 保證所有組件都小於25K
這個限制是因爲 iPhone 不能緩存大於 25K 的組件,注意這裏指的是 未壓縮的大小。
28. 把組件打包到一個複合文檔裏
把各個組件打包成一個像有附件的電子郵件一樣的複合文檔裏,可以用一個 HTTP 請求獲取多個組件(記住一點:HTTP 請求是代價高昂的)。用這種方式的時候,要先檢查用戶代理是否支持(iPhone就不支持)。
服務器
29. Gzip組件
壓縮可以通過減少 HTTP 響應的大小來縮短響應時間。
從 HTTP/1.1 開始,web 客戶端就有了支持壓縮的 Accept-Encoding HTTP
請求頭。
Accept-Encoding: gzip, deflate
如果 web 服務器看到這個請求頭,它就會用客戶端列出的一種方式來壓縮響應。web 服務器通過 Content-Encoding
相應頭來通知客戶端。
Content-Encoding: gzip
儘可能多地 用 gzip 壓縮 能夠給頁面減肥,這也是提升用戶體驗最簡單的方法。
30. 避免圖片src屬性爲空
src 屬性爲空 會引起 瀏覽器會向服務器發送另一個請求 的問題。
31. 配置ETags
實體標籤(ETags)
,是服務器和瀏覽器用來決定瀏覽器緩存中組件與源服務器中的組件是否匹配的一種機制(“實體”也就是組件:圖片,腳本,樣式表等等)。
添加 ETags 可以提供一種實體驗證機制,比最後修改日期更加靈活。一個 ETag 是一個字符串,作爲一個組件某一具體版本的唯一標識符。唯一的格式約束是字符串必須用引號括起來,源服務器用相應頭中的 ETag 來指定組件的 ETag:
HTTP/1.1 200 OK
Last-Modified: Tue, 12 Dec 2006 03:03:59 GMT
ETag: "10c24bc-4ab-457e1c1f"
Content-Length: 12195
然後,如果瀏覽器必須驗證一個組件,它用 If-None-Match
請求頭來把 ETag 傳回源服務器。如果 ETags 匹配成功,會返回一個 304 狀態碼,這樣就減少了 12195 個字節的響應體。
GET /i/yahoo.gif HTTP/1.1
Host: us.yimg.com
If-Modified-Since: Tue, 12 Dec 2006 03:03:59 GMT
If-None-Match: "10c24bc-4ab-457e1c1f"
HTTP/1.1 304 Not Modified
32. 對Ajax用GET請求
使用 XMLHttpRequest
時,瀏覽器的 POST 請求 是通過一個兩步的 過程 來實現的:先發送 HTTP 頭,再發送數據。所以 最好用 GET 請求,它 只需要發送一個 TCP 報文(除非 cookie 特別多)。
IE 的 URL 長度最大值是 2K,所以如果要發送的數據超過 2K 就無法使用GET了。
POST 請求的一個有趣的副作用是實際上沒有發送任何數據,就像 GET 請求一樣。正如 HTTP 說明文檔中描述的,GET 請求是用來檢索信息的。 所以它的語義只是用 GET 請求來請求數據,而不是用來發送需要存儲到服務器的數據。
33. 儘早清空緩衝區
當用戶請求一個頁面時,服務器需要用大約 200 到 500 毫秒
來組裝 HTML 頁面,在這期間,瀏覽器閒等着數據到達。
PHP 中有一個 flush()
函數,允許給瀏覽器發送一部分已經準備完畢的 HTML 響應,以便瀏覽器可以在後臺準備剩餘部分的同時開始獲取組件,好處 主要體現在很忙的後臺或者很“輕”的前端頁面上。
較理想的清空緩衝區的位置是 HEAD 後面,因爲 HTML 的 HEAD 部分通常更容易生成,並且允許引入任何 CSS 和 JavaScript 文件,這樣就可以讓瀏覽器在後臺還在處理的時候就開始並行獲取組件。
34. 使用CDN(內容分發網絡)
用戶與服務器的 物理距離 對響應時間也有影響。把內容部署在多個地理位置分散的服務器上能讓用戶更快地載入頁面。
最好先分散靜態內容,而不是一開始就重新設計應用程序結構。
內容分發網絡(CDN) 是一組分散在不同地理位置的 web 服務器,用來給用戶更高效地發送內容。典型地,選擇用來發送內容的服務器是基於網絡距離的衡量標準的。例如:選 跳數(hop) 最少的或者響應時間最快的服務器。
35. 添上Expires或者Cache-Control HTTP頭
- 對於靜態組件:通過設置一個遙遠的將來時間作爲 Expires 來實現永不失效。
- 對於動態組件:用合適的 Cache-ControlHTTP 頭來讓瀏覽器進行條件性的請求。
通過使用有效期能讓組件變得可緩存,這避免了在接下來的瀏覽過程中不必要的 HTTP 請求。有效期 HTTP 頭通常被用在圖片上,但它們應該用在所有組件上,包括腳本、樣式和 Flash 組件。
瀏覽器(和代理)用緩存來減少 HTTP 請求的數目和大小,讓頁面能夠更快加載。web 服務器通過 有效期 HTTP 響應頭 來告訴客戶端,頁面的各個組件應該被緩存多久。
用一個遙遠的將來時間做有效期,告訴瀏覽器這個響應在 2020 年4月15日前不會改變。
Expires: Thu, 15 Apr 2020 20:00:00 GMT
如果你用的是 Apache 服務器,用 ExpiresDefault
指令來設置相對於當前日期的有效期。
設置從請求時間起10年的有效期:
ExpiresDefault "access plus 10 years"```