no_mkd
寫在前面
英文原版連結,若是覺得本文哪裡不好還請指出,以便及時修改
目錄(分 7 類,共 35 條):
- [內容]儘量減少 HTTP 請求數
- [伺服器]使用 CDN(Content Delivery Network)
- [伺服器]添上 Expires 或者 Cache-Control HTTP 標頭
- [伺服器]Gzip 元件
- [css]把樣式表放在頂部
- [js]把腳本放在底部
- [css]避免使用 CSS 運算式
- [js, css]把 JavaScript 和 CSS 放到外面
- [內容]減少 DNS 查找
- [js, css]壓縮 JavaScript 和 CSS
- [內容]避免重新導向
- [js]去除重複腳本
- [伺服器]配置 ETags
- [內容]讓 Ajax 可快取
- [伺服器]儘早清空緩衝區
- [伺服器]對 Ajax 用 GET 請求
- [內容]延遲載入元件
- [內容]預載入元件
- [內容]減少 DOM 元素的數量
- [內容]跨域分離元件
- [內容]儘量少用 iframe
- [內容]杜絕 404
- [cookie]給 Cookie 減肥
- [cookie]把元件放在不含 Cookie 的網域下
- [js]儘量減少 DOM 存取
- [js]用智慧的事件處理器
- [css]選擇 捨棄 @import
- [css]避免使用濾鏡
- [圖片]優化圖片
- [圖片]優化 CSS Sprite
- [圖片]不要用 HTML 縮放圖片
- [圖片]用小的可快取的 favicon.ico(P.S. 收藏夾圖示)
- [移動端]保證所有元件都小於 25K
- [移動端]把元件打包到一個複合文件中
- [伺服器]避免圖片 src 屬性為空
譯文:給網站提速的最佳實踐
我們已經發現了不少給站點提速的最佳實踐,分 7 類共 35 條。
1. 儘量減少 HTTP 請求數
分類: 內容
80% 的終端使用者回應時間都花在了前端上,其中大部分時間都在下載頁面上的各種元件:圖片,樣式表,腳本,Flash 等等。減少元件數必然能夠減少頁面提交的 HTTP 請求數。這是讓頁面更快的關鍵。
減少頁面元件數的一種方式是簡化頁面設計。但有沒有一種方法可以在構建複雜的頁面同時加快回應時間呢?嗯,確實有魚與熊掌兼得的辦法。
合併檔案是透過把所有腳本放在一個檔案中的方式來減少請求數的,當然,也可以合併所有的 CSS。如果各個頁面的腳本和樣式不一樣的話,合併檔案就是一項比較麻煩的工作了,但把這個作為站點發布過程的一部分確實可以提高回應時間。
CSS Sprites 是減少圖片請求數量的首選方式。把背景圖片都整合到一張圖片中,然後用 CSS 的 background-image 和 background-position 屬性來定位要顯示的部分。
影像對映 可以把多張圖片合併成單張圖片,總大小是一樣的,但減少了請求數並加速了頁面載入。圖片對映只有在影像在頁面中連續的時候才有用,比如導覽列。給 image map 設置座標的過程既無聊又容易出錯,用 image map 來做導覽也不容易,所以不推薦用這種方式。
行內圖片(Base64 編碼) 用 data: URL 模式 來把圖片嵌入頁面。這樣會增加 HTML 檔案的大小,把行內圖片放在(快取的)樣式表中是個好辦法,而且成功避免了頁面變「重」。但目前主流瀏覽器並不能很好地支援行內圖片。
減少頁面的 HTTP 請求數是個起點,這是提升站點首次存取速度的重要指導原則。就像 Tenni Theurer 的部落格 Browser Cache Usage - Exposed! 裡寫到的,40% 到 60% 的訪客在存取你的站點時,快取都是空的。所以,加快頁面首次存取速度對提高使用者體驗是極其重要的。
2. 使用 CDN(Content Delivery Network)
分類: 伺服器
使用者與伺服器的物理距離對回應時間也有影響。把內容部署在多個地理位置分散的伺服器上能讓使用者更快地載入頁面。但具體要怎麼做呢?
實現內容在地理位置上分散的第一步是:不要嘗試去重新設計你的 Web 應用程式來適應分散式結構。這取決於應用程式,改變結構可能包括一些讓人望而生畏的工作,比如同步工作階段狀態和跨伺服器複製資料庫交易。縮短使用者和內容之間距離的提議可能被推遲,或者根本不可能通過,就是因為這個難題。
記住終端使用者 80% 到 90% 的回應時間都花在下載頁面元件上了:圖片,樣式,腳本,Flash 等等,這是業績黃金法則。最好先分散靜態內容,而不是一開始就重新設計應用程式結構。這不僅能夠大大減少回應時間,還更容易表現出 CDN 的功勞。
內容傳遞網路(CDN)是一組分散在不同地理位置的 Web 伺服器,用來給使用者更高效地發送內容。典型地,選擇用來發送內容的伺服器是基於網路距離的衡量標準的。例如:選跳數(hop)最少的或者回應時間最快的伺服器。
一些網際網路公司巨頭擁有他們自己的 CDN,但用一個 CDN 服務提供者是比較劃算的,比如 Akamai Technologies,EdgeCast,或者 level3。對剛剛起步的公司和個人網站來說,CDN 服務的成本是很高的���但如果你的使用者群卻越來越大,越來越全球化,那麼用 CDN 來換取更快的回應時間還是很有必要的。在 Yahoo!,把靜態內容從應用程式的 Web 伺服器搬到 CDN(包括上面提到的 3rd party 和 Yahoo 自己的 CDN)能夠提高終端使用者 20% 甚至更多的回應時間。換到 CDN 是一個相當簡單的程式碼變更,這將急劇提升站點的回應速度。
3. 添上 Expires 或者 Cache-Control HTTP 標頭
分類: 伺服器
這條規則有兩個方面:
- 對於靜態元件:透過設置一個遙遠的將來時間作為
Expires來實現永不失效 - 多餘動態元件:用合適的
Cache-ControlHTTP 標頭來讓瀏覽器進行條件性的請求
網頁設計越來越豐富,這意味著頁面裡有更多的腳本,圖片和 Flash。站點的新訪客可能還是不得不提交幾個 HTTP 請求,但透過使用有效期能讓元件變得可快取,這避免了在接下來的瀏覽過程中不必要的 HTTP 請求。有效期 HTTP 標頭通常被用在圖片上,但它們應該用在所有元件上,包括腳本、樣式和 Flash 元件。
瀏覽器(和代理伺服器)用快取來減少 HTTP 請求的數目和大小,讓頁面能夠更快載入。Web 伺服器透過有效期 HTTP 回應標頭來告訴用戶端,頁面的各個元件應該被快取多久。用一個遙遠的將來時間做有效期,告訴瀏覽器這個回應在 2010 年 4 月 15 日前不會改變。
Expires: Thu, 15 Apr 2010 20:00:00 GMT
如果你用的是 Apache 伺服器,用 ExpiresDefault 指令來設置相對於當前日期的有效期。下面的例子設置了從請求時間起 10 年的有效期:
ExpiresDefault "access plus 10 years"
記住,如果你用一個遙遠的未來時間做有效期,就不得不在元件發生變化後及時修改元件的檔案名稱。在 Yahoo!,我們經常把這一步作為建置過程的一部分:把版本號內嵌在元件的檔案名稱裡,例如:yahoo_2.0.6.js
用一個遙遠的未來時間做有效期 HTTP 標頭,只有在使用者已經存取過站點之後才會影響頁面視圖。如果是新訪客或者瀏覽器的快取被清空時,對 HTTP 請求的數量並沒有影響。因此這種效能提升取決於已快取各個元件的使用者存取站點的頻率。我們 在 Yahoo! 測量了這個數據,發現已快取各個元件的頁面瀏覽量(PV)佔 75% 到 85%。透過把一個遙遠的未來時間作為有效期 HTTP 標頭,增加了被瀏覽器快取的元件數量,在後續頁面瀏覽量中不需要用 Internet 連線多發送哪怕一個位元組。
4. Gzip 元件
分類: 伺服器
前端工程師可以想辦法明顯地縮短透過網路傳輸 HTTP 請求和回應的時間。毫無疑問,終端使用者的頻寬速度,網路服務商,對等交換點的距離等等,都是開發團隊所無法控制的。但還有別的能夠影響回應時間的因素,壓縮可以透過減少 HTTP 回應的大小來縮短回應時間。
從 HTTP/1.1 開始,Web 用戶端就有了支援壓縮的 Accept-Encoding HTTP 請求標頭。
Accept-Encoding: gzip, deflate
如果 Web 伺服器看到這個請求標頭,它就會用用戶端列出的一種方式來壓縮回應。Web 伺服器透過 Content-Encoding 相應頭來通知用戶端。
Content-Encoding: gzip
Gzip 是目前最常見的高效壓縮方法,由 GNU 專案開發並被 RFC 1952 標準化。唯一一個你可能會看到的其它壓縮格式是 deflate,但它效率不高而且並不常見。
Gzipping 一般能夠把回應壓縮到 70% 左右,目前大約 90% 的透過瀏覽器的網路傳輸都支援 gzip。如果是 Apache 伺服器,配置 gzip 的模組取決於版本:Apache 1.3 用 mod_gzip 而 Apache 2.x 是 mod_deflate 模組。
瀏覽器和代理伺服器的某些因素可能會引起瀏覽器所期望的和它收到的壓縮內容不匹配。幸運的是,隨著老舊瀏覽器的淘汰,這些極少遇到的情況正在逐漸減少,而且 Apache 模組可以透過自動添加合適的 Vary 回應標頭來幫你搞定。
伺服器會根據檔案類型來決定要不要用 gzip 壓縮,但這非常有限。大多數網站都用 gzip 壓縮 HTML 檔案,其實壓縮腳本,樣式表也是不錯的選擇,但很多網站卻錯失了這個機會。其實,可以壓縮任何文字內容,包括 XML 和 JSON,而圖片和 PDF 不用壓縮,因為它們已經被壓縮過了,再用 gzip 壓縮不僅浪費 CPU 還可能會越壓越大。
儘可能多地用 gzip 壓縮能夠給頁面減肥,這也是提升使用者體驗最簡單的方法。
5. 把樣式表放在頂部
分類: css
在 Yahoo! 研究效能的時候,我們發現把樣式表放到文件的 HEAD 部分能讓頁面看起來載入地更快。這是因為把樣式表放在 head 裡能讓頁面逐步渲染。
關注效能的前端工程師想讓頁面逐步渲染。也就是說,我們想讓瀏覽器儘快顯示已有內容,這在頁面上有一大堆內容或者使用者網速很慢時顯得尤為重要。給使用者顯示回饋(比如進度指標)的重要性已經被廣泛研究過,並且被 記錄 下來了。在我們的例子中,HTML 頁面就是進度指標!當瀏覽器逐漸載入頁面頭部,導覽列,頂部 logo 等等內容的時候,這些都被正在等待頁面載入的使用者當作回饋,能夠提高整體使用者體驗。
在很多瀏覽器(包括 IE)中,把樣式表放在 HTML 文件底部都會阻止頁面逐步渲染。這些瀏覽器阻塞渲染過程,以避免因為樣式變動而重繪頁面元素,使用者這時就只能盯著空白頁面。
HTML 官方文件 清楚地描述了樣式表應該放在頁面的 HEAD 裡面:"Unlike A, [LINK] may only appear in the HEAD section of a document, although it may appear any number of times."(不像 a 標籤,link 標籤可能只出現在 HEAD 部分,雖然它能可以出現任意多次)。空白螢幕或者沒有樣式的 flash 內容都是不可取的。理想方案就是遵循 HTML 官方文件,把樣式表放在 HTML 文件的 HEAD 部分。
6. 把腳本放在底部
分類: javascript
腳本會阻塞並行下載,HTTP/1.1 官方文件 建議瀏覽器每個主機名下並行下載的元件數不要超過兩個,如果圖片來自多個主機名,並行下載的數量就可以超過兩個。如果腳本正在下載,瀏覽器就不開始任何其它下載任務,即使是在不同主機名下的。
有時候,並不容易把腳本移動到底部。舉個例子,如果腳本是用 document.write 插入到頁面內容中的,就沒辦法再往下移了。還可能存在作用域問題,在多數情況下,這些問題都是可以解決的。
一個常見的建議是用推遲(deferred)腳本,有 DEFER 屬性的腳本意味著不能含有 document.write,並且提示瀏覽器告訴他們可以繼續渲染。不幸的是,Firefox 不支援 DEFER 屬性。在 IE 中,腳本可能被推遲,但不盡如人意。如果腳本可以推遲,我們就可以把它放到頁面底部,頁面就可以更快地載入。
7. 避免使用 CSS 運算式
分類: css
用 CSS 運算式動態設置 CSS 屬性,是一種強大又危險的方式。從 IE5 開始支援,但 從 IE8 起就不推薦使用了。例如,可以用 CSS 運算式把背景顏色設置成按小時交替的:
background-color: expression( (new Date()).getHours()%2 ? "#B8D4FF" : "#F08A00" );
上面的程式碼中,expression 方法可以接受一個 JavaScript 運算式。CSS 屬性會被設置成運算式的計算結果。expression 方法會被其它瀏覽器忽略,所以只有想辦法實現跨瀏覽器的與 IE 一致的使用者體驗才有用的。
運算式最大的問題是它們經常被重複計算,比我們想象的次數還要多。不僅僅是頁面渲染和調整大小的時候,在頁面被捲動,甚至使用者在頁面上移動滑鼠時都會重新計算運算式。給 CSS 運算式添加一個計數器就可以追蹤它重新計算的時間和頻率,而在頁面上動動滑鼠就可以引發 10000 多次重新計算。
減少 CSS 運算式重新計算的一種方式就是用一次性運算式,即在運算式第一次計算後就把樣式屬性設置成一個明確的值,換掉運算式。如果必須要在頁面的整個生命週期中動態設置樣式屬性,可以用事件處理器來代替 CSS 運算式。如果必須使用 CSS 運算式,要記得它們可能會被重複計算上千次,從而影響整個頁面的效能。
8. 把 JavaScript 和 CSS 放到外面
分類: javascript, css
很多效能原則都是關於如何管理外部元件的,然而,在這些顧慮出現之前你應該問一個更基礎的問題:應該把 JavaScript 和 CSS 放到外部檔案中還是直接寫在頁面裡?
實際上,用外部檔案可以讓頁面更快,因為 JavaScript 和 CSS 檔案會被快取在瀏覽器。HTML 文件中的行內 JavaScript 和 CSS 在每次請求該 HTML 文件的時候都會重新下載。這樣做減少了所需的 HTTP 請求數,但增加了 HTML 文件的大小。另一方面,如果 JavaScript 和 CSS 在外部檔案中,並且已經被瀏覽器快取起來了,那麼我們就成功地把 HTML 文件變小了,而且還沒有增加 HTTP 請求數。
關鍵因素是,外部檔案被快取的頻率和頁面被請求數量之間的關係。儘管這個因素很難量化,但我們還是可以用各種各樣的指標來衡量。如果使用者的每個會話中都有多次頁面存取,那麼相同的腳本和樣式表就可以被多個頁面複用,快取的外部檔案就會帶來巨大的好處。
很多站點在度量中都處於中等水平,對這些站點來說,一般最好的解決方案就是把 JavaScript 和 CSS 部署為外部檔案。唯一的例外是主頁上行內方式優先,例如 Yahoo! 的首頁 和 My Yahoo!。在每個會話中存取量比較少的主頁會發現行內 JavaScript 和 CSS 能讓終端使用者的回應時間更快。
對典型的站點來說,首頁是眾多存取量的開始,有很多技術可以對減少 HTTP 請求起到槓桿作用,就像用外部檔案快取的好處一樣。這樣的一種技術就是在大門頁面用行內 JavaScript 和 CSS,但在頁面載入完成之後動態載入外部檔案,這樣後續的頁面所需的外部檔案就已經被放到瀏覽器的快取裡了。
9. 減少 DNS 查找
分類: 內容
網域名稱系統建立了主機名稱和 IP 位址間的映射,就像電話簿上人名和號碼的映射一樣。當你在瀏覽器輸入 www.yahoo.com 的時候,瀏覽器就會聯繫 DNS 解析器回傳伺服器的 IP 位址。DNS 是有成本的,它需要 20 到 120 毫秒去查找給定主機名稱的 IP 位址。在 DNS 查找完成之前,瀏覽器無法從主機名稱下載任何東西。
DNS 查找被快取起來更高效,由使用者的 ISP(網路服務提供者)或者本機網路存在一個特殊的快取伺服器上,但還可以快取在個人使用者的電腦上。DNS 資訊被保存在作業系統的 DNS 快取(微軟 Windows 上的 "DNS 用戶端服務")裡。大多數瀏覽器有獨立於作業系統的自己的快取。只要瀏覽器在自己的快取裡還保留著這條記錄,它就部會向作業系統查詢 DNS。
IE 預設快取 DNS 查找 30 分鐘,寫在 DnsCacheTimeout 登錄表設置中。Firefox 快取 1 分鐘,可以用 network.dnsCacheExpiration 配置項設置。(Fasterfox 把快取時間改成了 1 小時 P.S. Fasterfox 是 FF 的一個提速外掛程式)
如果用戶端的 DNS 快取是空的(包括瀏覽器的和作業系統的),DNS 查找數等於頁面上不同的主機名稱數,包括頁面 URL,圖片,腳本檔案,樣式表,Flash 物件等等元件中的主機名稱,減少不同的主機名稱就可以減少 DNS 查找。
減少不同主機名稱的數量同時也減少了頁面能夠並行下載的元件數量,避免 DNS 查找削減了回應時間,而減少並行下載數量卻增加了回應時間。我的原則是把元件分散在 2 到 4 個主機名稱下,這是同時減少 DNS 查找和允許高併發下載的折衷方案。
10. 壓縮 JavaScript 和 CSS
分類: javascript, css
壓縮具體來說就是從程式碼中去除不必要的字元以減少大小,從而提升載入速度。程式碼最小化就是去掉所有註解和不必要的空白字元(空格,換行和 tab)。在 JavaScript 中這樣做能夠提高回應效能,因為要下載的檔案變小了。兩個最常用的 JavaScript 程式碼壓縮工具是 JSMin 和 YUI Compressor,YUI compressor 還可以壓縮 CSS。
混淆是一種可選的源碼優化措施,要比壓縮更複雜,所以混淆過程也更容易產生 bug。在對美國前十的網站調查中,壓縮可以縮小 21%,而混淆能縮小 25%。雖然混淆的縮小程度更高,但比壓縮風險更大。
除了壓縮外部腳本和樣式,行內的 <script> 和 <style> 塊也可以壓縮。即使啟用了 gzip 模組,先進行壓縮也能夠縮小 5% 或者更多的大小。JavaScript 和 CSS 的用處越來越多,所以壓縮程式碼會有不錯的效果。
11. 避免重新導向
分類: 內容
重新導向用 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,但如果你必須得做重新導向,最好用標準的 3xx HTTP 狀態碼,主要是為了讓返回按鈕能正常使用。
牢記重新導向會拖慢使用者體驗,在使用者和 HTML 文件之間插入重新導向會延遲頁面上的所有東西,頁面無法渲染,元件也無法開始下載,直到 HTML 文件被送達瀏覽器。
有一種常見的極其浪費資源的重新導向,而且 Web 開發人員一般都意識不到這一點,就是 URL 尾部缺少一個斜線的時候。例如,跳轉到 http://astrology.yahoo.com/astrology 會回傳一個重新導向到 http://astrology.yahoo.com/astrology/ 的 301 回應(注意添在尾部的斜線)。在 Apache 中可以用 Alias,mod_rewrite 或者 DirectorySlash 指令來取消不必要的重新導向。
重新導向最常見的用途是把舊站點連接到新的站點,還可以連接同一站點的不同部分,針對使用者的不同情況(瀏覽器類型,使用者帳號類型等等)做一些處理。用重新導向來連接兩個網站是最簡單的,只需要少量的額外程式碼。雖然在這些時候使用重新導向減少了開發人員的開發複雜度,但降低了使用者體驗。一種替代方案是用 Alias 和 mod_rewrite,前提是兩個程式碼路徑都在相同的伺服器上。如果是因為網域變化而使用了重新導向,就可以建立一條 CNAME(建立一個指向另一個網域的 DNS 記錄作為別名)結合 Alias 或者 mod_rewrite 指令。
12. 去除重複腳本
分類: javascript
頁面含有重複的腳本檔案會影響效能,這可能和你想象的不一樣。在對美國前 10 大 Web 站點的評審中,發現只有 2 個站點含有重複腳本。兩個主要原因增加了在單一頁面中出現重複腳本的機率:團隊大小和腳本數量。在這種情況下,重複腳本會建立不必要的 HTTP 請求,執行無用的 JavaScript 程式碼,而影響頁面效能。
IE 會產生不必要的 HTTP 請求,而 Firefox 不會。在 IE 中,如果一個不可快取的外部腳本被頁面引入了兩次,它會在頁面載入時產生兩個 HTTP 請求。即使腳本是可快取的,在使用者重新載入頁面時也會產生額外的 HTTP 請求。
除了產生沒有意義的 HTTP 請求之外,多次對腳本求值也會浪費時間。因為無論腳本是否可快取,在 Firefox 和 IE 中都會執行冗餘的 JavaScript 程式碼。
避免不小心把相同腳本引入兩次的一種方法就是在範本系統中實現腳本管理模組。典型的腳本引入方法就是在 HTML 頁面中用 SCRIPT 標籤:
<script type="text/javascript" src="menu_1.0.17.js"></script>
PHP 中一個可選方案是建立一個叫 insertScript 的函式:
<?php insertScript("menu.js") ?>
除了防止相同腳本被多次引入,這個函式還可以解決腳本相關的其它問題,比如依賴性檢查和給腳本檔案名稱添加版本號來支援「永久」有效期 HTTP 標頭。
13. 配置 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
ETags 存在的問題是它們是由特定伺服器構造的,所以服務器從一個伺服器獲取最初的元件,然後想驗證另一個伺服器上的相同元件,ETags 是無法匹配成功的,而用一群伺服器處理請求在 Web 站點中又非常普遍。預設情況下,Apache 和 IIS 會在 ETag 中嵌入數據,以大大降低在多伺服器站點上有效性測試成功的機率。
Apache 1.3 和 2.x 中 ETag 的格式是 inode-size-timestamp。就算給定的檔案可能在多個伺服器的相同目錄下,而且檔案大小、存取權限、時間戳記等等全部相同,它的 i 節點(P.S. inode,UNIX 中的索引檔案)在不同伺服器中也不一樣。
IIS5.0 和 6.0 也都存在類似的問題。IIS 中 ETags 的格式是 Filetimestamp:ChangeNumber,ChangeNumber 是一個用來追蹤 IIS 配置變更的計數器。 一個站點在不同的 IIS 伺服器上的 ChangeNumber 是不可能相同的。
最終結果是 Apache 和 IIS 為完全相同的元件生成的 ETags 無法跨瀏覽器匹配,如果 ETags 不匹配,使用者就無法收到為又小又快的 304 回應設計的 ETags。反而,他們將收到一個攜帶著元件所有數據的 200 正常回應。如果站點部署在單一伺服器上,就根本不存在這個問題。但如果站點部署在多個伺服器上,而且打算用 Apache 或者 IIS 的預設 ETags 配置,使用者將看到緩慢的頁面,伺服器負載更高,還會消耗更大的頻寬,並且代理伺服器也無法有效快取頁面內容。即使元件有「永久」 Expires HTTP 標頭,使用者點擊重新載入或者重新整理的時候,仍然會發出條件 GET 請求。
如果不想用 ETags 提供的靈活的驗證模型,最好把所有的 Etag 全都去掉,可以用基於元件的時間戳記的 Last-Modified HTTP 標頭驗證,而且去掉 ETag 可以減少 HTTP 回應標頭以及後續請求的大小。Microsoft Support article 裡寫了怎樣移除 ETags。在 Apache 中,可以簡單地透過在 Apache 設定檔中添上如下程式碼來實現:
FileETag none
14. 讓 Ajax 可快取
分類: 內容
Ajax 的一個好處是可以給使用者提供即時回饋,因為它能夠從後台伺服器非同步請求資訊。然而,用了 Ajax 就無法保證使用者在等待非同步 JavaScript 和 XML 回應回傳期間不會非常無聊。在很多應用程式中,使用者能夠一直等待取決於如何使用 Ajax。例如,在基於 Web 的電子郵件用戶端中,使用者為了尋找符合他們搜尋標準的郵件訊息,將會保持對 Ajax 請求回傳結果的關注。重要的是,要記得「非同步」並不意味著「即時」。
要提高效能,優化這些 Ajax 回應至關重要。最重要的提高 Ajax 效能的方法就是讓回應變得可快取,就像在 添上 Expires 或者 Cache-Control HTTP 標頭 中討論的一樣。下面適用於 Ajax 的其它規則:
我們一起看看例子,一個 Web 2.0 的電子郵件用戶端用了 Ajax 來下載使用者的通訊錄,以便實現自動完成功能。如果使用者從上一次使用之後再沒有修改過她的通訊錄,而且 Ajax 回應是可快取的,有尚未過期的 Expires 或者 Cache-Control HTTP 標頭,那麼之前的通訊錄就可以從快取中讀出。必須通知瀏覽器,應該繼續使用之前快取的回應,還是去請求一個新的。可以透過給通訊錄的 Ajax URL 裡添加一個表明使用者通訊錄最後修改時間的時間戳記來實現,例如 &t=1190241612。如果通訊錄從上一次下載之後再沒有被修改過,時間戳記不變,通訊錄就將從瀏覽器快取中直接讀出,從而避免一次額外的 HTTP 往返消耗。如果使用者已經修改了通訊錄,時間戳記也可以確保新的 URL 不會匹配快取的回應,瀏覽器將請求新的通訊錄項目。
即使 Ajax 回應是動態建立的,而且可能只適用於單一使用者,它們也可以被快取,而這樣會讓你的 Web 2.0 應用更快。
15. 儘早清空緩衝區
分類: 伺服器
當使用者請求一個頁面時,伺服器需要用大約 200 到 500 毫秒來組裝 HTML 頁面,在這期間,瀏覽器閒等著數據到達。PHP 中有一個 flush() 函式,允許給瀏覽器發送一部分已經準備完畢的 HTML 回應,以便瀏覽器可以在後台準備剩餘部分的同時開始獲取元件,好處主要體現在很忙的後台或者很「輕」的前端頁面上(P.S. 也就是說,回應時耗主要在後端方面時最能體現優勢)。
比較理想的清空緩衝區的位置是 HEAD 後面,因為 HTML 的 HEAD 部分通常更容易生成,並且允許引入任何 CSS 和 JavaScript 檔案,這樣就可以讓瀏覽器在後台還在處理的時候開始並行獲取元件。
例如:
... <!-- css, js -->
</head>
<?php flush(); ?>
<body>
... <!-- content -->
Yahoo! 搜尋 開創了這項技術,而且真實使用者測試研究也證明了使用這種技術的諸多好處。
16. 對 Ajax 用 GET 請求
分類: 伺服器
Yahoo! 信箱 團隊發現使用 XMLHttpRequest 時,瀏覽器的 POST 請求是透過一個兩步的過程來實現的:先發送 HTTP 標頭,再發送數據。所以最好用 GET 請求,它只需要發送一個 TCP 封包(除非 Cookie 特別多)。IE 的 URL 長度最大值是 2K,所以如果要發送的數據超過 2K 就無法使用 GET 了。
POST 請求的一個有趣的副作用是實際上沒有發送任何數據,就像 GET 請求一樣。正如 HTTP 說明文件 中描述的,GET 請求是用來檢索資訊的。所以它的語義只是用 GET 請求來請求數據,而不是用來發送需要存儲到伺服器的數據。
17. 延遲載入元件
分類: 內容
可以湊近看看頁面並問自己:什麼才是一開始渲染頁面所必須的?其餘內容都可以等一下。
JavaScript 是分隔 onload 事件之前和之後的一個理想選擇。例如,如果有 JavaScript 程式碼和支援拖放以及動畫的程式庫,這些都可以先等一下,因為拖放元素是在頁面最初渲染之後的。其它可以延遲載入的部分包括隱藏內容(在某個交互動作之後才出現的內容)和折疊的圖片。
工具可幫你減輕工作量:YUI Image Loader 可以延遲載入折疊的圖片,還有 YUI Get utility 是一種引入 JS 和 CSS 的簡單方法。Yahoo! 首頁 就是一個例子,可以打開 Firebug 的網路面板仔細看看。
最好讓效能目標符合其它 Web 開發最佳實踐,比如「漸進增強」。如果用戶端支援 JavaScript,可以提高使用者體驗,但必須確保頁面在不支援 JavaScript 時也能正常工作。所以,在確定頁面執行正常之後,可以用一些延遲載入腳本增強它,以支援一些拖放和動畫之類的華麗效果。
18. 預載入元件
分類: 內容
預載入可能看起來和延遲載入是相反的,但它其實有不同的目標。透過預載入元件可以充分利用瀏覽器空閒的時間來請求將來會用到的元件(圖片,樣式和腳本)。使用者存取下一頁的時候,大部分元件都已經在快取裡了,所以在使用者看來頁面會載入得更快。
實際應用中有以下幾種預載入的類型:
- 無條件預載入——儘快開始載入,獲取一些額外的元件。google.com 就是一個 Sprite 圖片預載入的好例子,這個 Sprite 圖片並不是 google.com 主頁需要的,而是搜尋結果頁面上的內容。
- 條件性預載入——根據使用者操作猜測使用者將要跳轉到哪裡並據此預載入。在 search.yahoo.com 的輸入框裡鍵入內容後,可以看到那些額外元件是怎樣請求載入的。
- 提前預載入——在推出新設計之前預載入。經常在重新設計之後會聽到:「這個新網站不錯,但比以前更慢了」,一部分原因是使用者存取先前的頁面都是有舊快取的,但新的卻是一種空快取狀態下的體驗。可以透過在將要推出新設計之前預載入一些元件來減輕這種負面影響,老站可以利用瀏覽器空閒的時間來請求那些新站需要的圖片和腳本。
19. 減少 DOM 元素的數量
分類: 內容
一個複雜的頁面意味著要下載更多的位元組,而且用 JavaScript 存取 DOM 也會更慢。舉個例子,想要添加一個事件處理器的時候,迴圈走訪頁面上的 500 個 DOM 元素和 5000 個 DOM 元素是有區別的。
大量的 DOM 元素是一種徵兆——頁面上有些內容無關的標記需要清理。正在用巢狀表格來佈局嗎?還是為了修復佈局問題而添了一堆的 <div>s?或許應該用更好的語義化標記。
YUI CSS utilities 對佈局有很大幫助:grids.css 針對整體佈局,fonts.css 和 reset.css 可以用來去除瀏覽器的預設格式。這是個開始清理和思考標記的好機會,例如只在語義上有意義的時候使用 <div>,而不是因為它能夠渲染一個新行。
DOM 元素的數量很容易測試,只需要在 Firebug 的控制台裡輸入:
document.getElementsByTagName('*').length
那麼多少 DOM 元素才算是太多呢?可以參考其它類似的標記良好的頁面,例如 Yahoo! 首頁 是一個相當繁忙的頁面,但只有不到 700 個元素(HTML 標籤)。
20. 跨域分離元件
分類: 內容
分離元件可以最大化並行下載,但要確保只用不超過 2-4 個域,因為存在 DNS 查找的代價。例如,可以把 HTML 和動態內容部署在 www.example.org,而把靜態元件分離到 static1.example.org 和 static2.example.org。
更多資訊請查看 Tenni Theurer 和 Patty Chi 的文章:Maximizing Parallel Downloads in the Carpool Lane
21. 儘量少用 iframe
分類: 內容
用 iframe 可以把一個 HTML 文件插入到父文件中,重要的是明白 iframe 是如何工作的並高效地使用它。
<iframe> 的優點:
- 引入緩慢的第三方內容,比如標誌和廣告
- 安全沙箱
- 並行下載腳本
<iframe> 的缺點:
- 代價高昂,即使是空白的 iframe
- 阻塞頁面載入
- 非語義
22. 杜絕 404
分類: 內容
HTTP 請求代價高昂,完全沒有必要用一個 HTTP 請求去獲取一個無用的回應(比如 404 Not Found),只會拖慢使用者體驗而沒有任何好處。
有些站點用的是有幫助的 404——「你的意思是 xxx?」,這樣做有利於使用者體驗,但也浪費了伺服器資源(比如資料庫等等)。最糟糕的是連結到的外部 JavaScript 有錯誤而且結果是 404。首先,這種下載將阻塞並行下載。其次,瀏覽器會試圖解析 404 回應體,因為它是 JavaScript 程式碼,需要找出其中可用的部分。
23. 給 Cookie 減肥
分類: cookie
使用 Cookie 的原因有很多,比如授權和個性化。HTTP 標頭中 Cookie 資訊在 Web 伺服器和瀏覽器之間交換。重要的是保證 Cookie 儘可能的小,以最小化對使用者回應時間的影響。
更多資訊請查看 Tenni Theurer 和 Patty Chi 的文章:When the Cookie Crumbles。相關經驗原則可以總結如下:
- 清除不必要的 Cookie
- 保證 Cookie 儘可能小,以最小化對使用者回應時間的影響
- 注意給 Cookie 設置合適的網域級別,以免影響其它子網域
- 設置合適的有效期,更早的有效期或者 none 可以更快的刪除 Cookie,提高使用者回應時間
24. 把元件放在不含 Cookie 的網域下
分類: cookie
當瀏覽器發送對靜態影像的請求時,Cookie 也會一起發送,而伺服器根本不需要這些 Cookie。所以它們只會造成沒有意義的網路通訊量,應該確保對靜態元件的請求不含 Cookie。可以建立一個子網域,把所有的靜態元件都部署在那兒。
如果網域名稱是 www.example.org,可以把靜態元件部署到 static.example.org。然而,如果已經在頂級網域 example.org 或者 www.example.org 設置了 Cookie,那麼所有對 static.example.org 的請求都會含有這些 Cookie。這時候可以再買一個新網域名稱,把所有的靜態元件部署上去,並保持這個新網域名稱不含 Cookie。Yahoo! 用的是 yimg.com,YouTube 是 ytimg.com,Amazon 是 images-amazon.com 等等。
把靜態元件部署在不含 Cookie 的網域下還有一個好處是有些代理伺服器可能會拒絕快取帶 Cookie 的元件。有一點需要注意:如果不知道應該用 example.org 還是 www.example.org 作為主頁,可以考慮一下 Cookie 的影響。省略 www 的話,就只能把 Cookie 寫到 *.example.org,所以因為效能原因最好用 www 子網域,並且把 Cookie 寫到這個子網域下。
25. 儘量減少 DOM 存取
分類: javascript
用 JavaScript 存取 DOM 元素是很慢的,所以,為了讓頁面反應更迅速,應該:
- 快取已存取過的元素的索引
- 先「離線」更新節點,再把它們添到 DOM 樹上
- 避免用 JavaScript 修復佈局問題
更多資訊請查看 YUI 影院裡 Julien Lecomte 的文章:High Performance Ajax Applications
26. 用智慧的事件處理器
分類: javascript
有時候感覺頁面反應不夠靈敏,是因為有太多頻繁執行的事件處理器被添加到了 DOM 樹的不同元素上,這就是推薦使用事件委託的原因。如果一個 div 裡面有 10 個按鈕,應該只給 div 容器添加一個事件處理器,而不是給每個按鈕都添加一個。事件能夠冒泡,所以可以捕獲事件並得知哪個按鈕是事件源。
不需要為了處理 DOM 樹而等待 onload 事件,通常只要目標元素在 DOM 樹中可存取即可,而不必等待所有的圖片���載完成。可以考慮用 DOMContentLoaded 來代替 onload 事件,但為了讓它在所有瀏覽器中都可用,可以用 YUI Event 工具,它有一個 onAvailable 方法。
更多資訊請查看 YUI 影院裡 Julien Lecomte 的文章:High Performance Ajax Applications
27. 選擇 <link> 捨棄 @import
分類: css
前面提到了一個最佳實踐:為了實現逐步渲染,CSS 應該放在頂部。
在 IE 中用 @import 與在底部用 <link> 效果一樣,所以最好不要用它。
28. 避免使用濾鏡
分類: css
IE 專有的 AlphaImageLoader 濾鏡可以用來修復 IE7 之前的版本中半透明 PNG 圖片的問題。在圖片載入過程中,這個濾鏡會阻塞渲染,卡住瀏覽器,還會增加記憶體消耗而且是被應用到每個元素的,而不是每個圖片,所以會存在一大堆問題。
最好的方法是乾脆不要用 AlphaImageLoader,而優雅地降級到用在 IE 中支援性很好的 PNG8 圖片來代替。如果非要用 AlphaImageLoader,應該用底線 hack:_filter 來避免影響 IE7 及更高版本的使用者。
29. 優化圖片
分類: 圖片
設計師做好圖片後,在把這些圖片透過 FTP 上傳到 Web 伺服器之前,我們還可以做一些事情。
- 檢查 GIF 圖片,看看圖片中是不是用了調色盤大小對應的顏色數,用 imagemagick 可以簡單地檢查:
identify -verbose image.gif
如果 4 色圖片用了調色盤中 256 色的「槽」,那還有改進的餘地 - 試著把 GIF 圖片轉換成 PNG,看能不能縮減大小,往往可以。開發者通常不願意用 PNG 圖片,因為瀏覽器支援有限,但這都是過去的事情了。真正的問題是 PNG 圖片完全支援 alpha 透明度,而 GIF 圖片卻不支援透明度漸變,所以 GIF 能做的任何事情,PNG 都可以(除了動畫)。下面這個簡單的命令就能讓 PNG 圖片可以安全使用:
convert image.gif image.png
「我們強調的是:給 PNG 一個機會。」 - 用 pngcrush(或者其他的 PNG 優化工具)處理所有的 PNG 圖片,例如:
pngcrush image.png -rem alla -reduce -brute result.png - 用 jpegtran 處理所有 JPEG 圖片,這個工具支援對 JPEG 圖片的無損操作比如旋轉,還可以用來去除註解和其它無用資訊(比如 EXIF 資訊 P.S. 數碼照片資訊,焦距光圈之類的):
jpegtran -copy none -optimize -perfect src.jpg dest.jpg
30. 優化 CSS Sprite
分類: 圖片
- 在 Sprite 圖片中橫向排列一般都比縱向排列的最終檔案小
- 組合 Sprite 圖片中的相似顏色可以保持低色數,最理想的是 256 色以下 PNG8 格式
- 「對移動端友好」,不要在 Sprite 圖片中留下太大的空隙。雖然不會在很大程度上影響圖片檔案的大小,但這樣做可以節省使用者代理把圖片解壓成像素映射時消耗的記憶體。100x100 的圖片是 1 萬個像素,而 1000x1000 的圖片就是 100 萬個像素了。
31. 不要用 HTML 縮放圖片
分類: 圖片
不要因為在 HTML 中可以設置寬高而使用本不需要的大圖。如果需要
<img width="100" height="100" src="mycat.jpg" alt="My Cat" />
那麼圖片本身(mycat.jpg)應該是 100x100px 的,而不是去縮小 500x500px 的圖片。
32. 用小的可快取的 favicon.ico(P.S. 收藏夾圖示)
分類: 圖片
favicon.ico 是放在伺服器根目錄的圖片,它會帶來一堆麻煩,因為即便你不管它,瀏覽器也會自動請求它,所以最好不要給一個 404 Not Found 回應。而且只要在同一個伺服器上,每次請求它時都會發送 Cookie,此外這個圖片還會干擾下載順序,例如在 IE 中,當你在 onload 中請求額外元件時,將會先下載 favicon。
所以為了緩解 favicon.ico 的缺點,應該確保:
- 足夠小,最好在 1K 以下
- 設置合適的有效期 HTTP 標頭(以後如果想換的話就不能重新命名了),把有效期設置為幾個月後一般比較安全,可以透過檢查當前 favicon.ico 的最後修改日期來確保變更能讓瀏覽器知道。
Imagemagick 可以用來處理小收藏夾圖示
33. 保證所有元件都小於 25K
分類: 移動
這個限制是因為 iPhone 不能快取大於 25K 的元件,注意這裡指的是未壓縮的大小。這就是為什麼縮減內容本身也很重要,因為單純的 gzip 可能不夠。
更多資訊請查看 Wayne Shea 和 Tenni Theurer 的文章:Performance Research, Part 5: iPhone Cacheability - Making it Stick
34. 把元件打包到一個複合文件中
分類: 移動
把各個元件打包成一個像有附件的電子郵件一樣的複合文件中,可以用一個 HTTP 請求獲取多個元件(記住一點:HTTP 請求是代價高昂的)。用這種方式的時候,要先檢查使用者代理是否支援(iPhone 就不支援)。
35. 避免圖片 src 屬性為空
分類: 伺服器
Image with empty string src 屬性是空字串的圖片很常見,主要以兩種形式出現:
- straight HTML
<img src="">
- JavaScript
var img = new Image();
img.src = "";
這兩種形式都會引起相同的問題:瀏覽器會向伺服器發送另一個請求。
- IE 向頁面所在目錄發起一個請求
- Safari 和 Chrome 向當前頁面本身發送一個請求
- Firefox 3 及更早版本與 Safari 和 Chrome 處理方式一樣,但 3.5 解決了這個問題 [bug 444931],不會再發送請求了
- Opera 遇到有空 src 屬性的圖片不做任何處理
為什麼圖片 src 屬性為空不好?
- 意外發送大量的通訊量對伺服器來說是很傷的,尤其是在每天有幾百萬存取量頁面的時候。
- 浪費伺服器資源去生成一個根本不可能被看到的頁面
- 可能會污染使用者數據,如果追蹤請求狀態,要麼透過 Cookie 要麼是其它方式,可能會破壞使用者數據。即使圖片請求並沒有回傳圖片,整個 HTTP 頭部也會被瀏覽器接受並讀取,包括所有的 Cookie。雖然其餘部分會被丟棄,但這可能已經造成破壞了。
問題的根本原因是各個瀏覽器在處理 URI 時的分歧,這在 RFC 3986 - Uniform Resource Identifiers 文件中有明確定義。如果 URI 是一個空字串,會被看作一個相對 URI,並按照 5.2 節定義的演算法處理。實際情況是,Firefox、Safari 和 Chrome 都是根據文件中 5.4 節列出的規範來處理空字串,而 IE 並沒有正確處理。據說在舊版本規範文件 RFC 2396 - Uniform Resource Identifiers(被 RFC 3986 廢棄了)中,所以從嚴格意義上來說瀏覽器處理相對 URI 的做法都是對的。問題是,在這種情形下,空字串顯然是無心的(P.S. 而不是什麼相對 URI)。
HTML5 的 4.8.2 節有關於 <img> 標籤 src 屬性的描述,規定瀏覽器不再發送額外請求:
The src attribute must be present, and must contain a valid URL referencing a non-interactive, optionally animated, image resource that is neither paged nor scripted. If the base URI of the element is the same as the document's address, then the src attribute's value must not be the empty string.(P.S. 「src 屬性必須存在,而且必須有一個合法的 URL 引用非交互式的動畫或者圖像資源,不能分頁也不能含有腳本。如果元素的基 URI 和文件地址相同,那麼 src 屬性的值就不能是空字串。」)
希望將來瀏覽器不會存在這個問題,不幸的是,沒有針對 <script src=""> 和 <link href=""> 的條款,也許還有時間調整,以確保瀏覽器不會意外地實現這一行為。
這條原則是受了 Yahoo! 的 JavaScript 大師 Nicolas C. Zakas 的啟發,更多資訊請查看他的文章:Empty image src can destroy your site
暫無評論,快來發表你的看法吧