本文內容 提高 Web 站點性能的最佳實踐 修改記錄 下麵是使頁面更快的35個最佳實踐,它們被劃分為7個類別。 類別: content、server、cookie、css、javascript、images、mobile (Yahoo 開發的瀏覽器插件 YSLOW,利用這七個類作為評價頁面的指標) ...
本文內容
- 提高 Web 站點性能的最佳實踐
- 最大限度減少 HTTP 請求
- 使用內容分髮網絡(CDN)
- 添加 Expires 或 Cache – Control 頭
- Gzip 組件
- CSS 放在頁面頂部
- JavaScript 放在頁面底部
- 避免 CSS 表達式
- 使用外部 JavaScript 和 CSS
- 減少 DNS 查詢
- 精簡 JavaScript 和 CSS
- 避免重定向
- 刪除重覆的腳本
- 配置 ETags
- 使得 Ajax 可緩存
- 儘早強制地發送緩衝給客戶端
- 用 GET 發送 Ajax 請求
- 延遲載入組件
- 預載入組件
- 減少 DOM 元素數量
- 根據功能變數名稱劃分頁面內容
- 最小化 iframe 數量
- 不要出現 404 錯誤
- 減小 Cookie 的大小
- 對組件使用無 coockie 功能變數名稱
- 最小化 DOM 訪問
- 開發智能的事件處理程式
- 用 <link> 代替 @import
- 避免使用濾鏡
- 優化圖像
- 優化 CSS Sprites
- 不要在 HTML 中縮放圖像
- favicon.ico 要小且可緩存
- 保持組件 25K 以下
- 把組件打包到一個 Multipart 文檔
- 避免圖片 src 屬性為空
- 修改記錄
下麵是使頁面更快的35個最佳實踐,它們被劃分為7個類別。
類別: content、server、cookie、css、javascript、images、mobile
(Yahoo 開發的瀏覽器插件 YSLOW,利用這七個類作為評價頁面的指標)
最大限度減少 HTTP 請求
類別: content
最終用戶(the end-user)80%的響應時間花費在前端(the front-end)。大部分時間用來下載頁面中的所有組件:圖像、CSS、JS、Flash 等。因此,反過來,減少頁面組件的數量,就可以減少渲染(呈現)頁面所需的 HTTP 請求的數量。這是加快頁面的關鍵。
一個方法當然是簡化頁面設計,減少頁面組件的數量。但是否有方法,既構建具有豐富內容的網頁,也實現了快速響應?下麵是減少 HTTP 請求數量的技巧,也提供了豐富的網頁設計。
- 合併文件。通過把所有腳本或 CSS 合併到一個單獨的文件,來減少 HTTP 請求的數量。當在不同的頁面中,腳本和 CSS 都不太一樣時,合併就比較困難。你可以把合併放到最後部署時,從而改進響應時間。
- CSS 精靈(CSS Sprites)。CSS Sprites是減少圖像請求數量的首選方法。把你的背景圖像合併到一個單獨的圖像,並且使用CSS 的“background-image”和“background-position”屬性,來顯示你所需的圖像部分。
CSS Sprites 是一種 CSS 圖像拼合技術,一種網頁圖片應用處理方式。
- 圖像地圖(Image maps)。把多個圖像合併到一個單獨的圖像。合併前與合併後的圖像總體大小相同,而且減少了HTTP請求的數量,加快了頁面速度。但只有當圖像在頁面中是連續的,Image maps才好用,如導航欄。定義圖像地圖的坐標很枯燥,而且容易出錯。對導航使用圖像地圖不具有可訪問性,所以不推薦。
- 內嵌圖片(Inline images)。在頁面中,使用 data: URL scheme 嵌入圖像數據。這會增加頁面大小。Inline images 與 CSS(已緩存)相結合可以減少 HTTP 請求,避免增加頁面大小。目前,所有主流瀏覽器尚不支持 Inline images。
減少頁面的HTTP請求的數量首選要做的事,對於改善用戶初次訪問頁面的性能,這是最重要準則。正如 Tenni Theurer 在其文章 Browser Cache Usage - Exposed! 指出的,每天訪問你網站的 40-60% 人都是無緩存的(都是初次訪問,無本地緩存)。使頁面對初次訪問更快,是更好的用戶體驗的關鍵。這些首次訪問者的頁面快速更好的用戶體驗是關鍵。
批註
一個頁面往往包含很多資源,比如圖像、JS、CSS 等,而這些資源都在伺服器上,客戶端若想顯示這個頁面,必須通過網路從伺服器下載資源到本地。可想而知,雖然每個資源都很小,也就在幾十 K 左右,但是數量很多,客戶端每次需要這樣資源,都會到伺服器上下載,因此,減少下載的次數,或是下載完後,直接從瀏覽器緩存里獲得,將很有意義。
本節的技巧:合併文件、CSS Sprites、Image maps、Inline images 都是為了減少下載次數。比如,如果一個頁面需要三個 CSS,那麼在用戶初次訪問頁面時,需要向伺服器請求三次。因此,若將這三個 CSS 合併成一個,那麼只需下載一次;圖像也是如此,一個頁面最多的就是圖像,試想網站的導航條,與其為每個操作都搞一個圖片,倒不如將這些圖片合併在一個圖像上,再通過 CSS 獲得圖片局部區域。
使用內容分髮網絡(CDN)
類別: server
用戶“接近”你Web伺服器的程度會影響響應時間。把內容部署在多個、地理位置分散的伺服器上,會使頁面載入的速度從用戶角度看更快。但是我們應該從哪裡開始?
作為實現地理位置分散內容的第一步,不要試圖重新設計你的Web應用程式,使它運行在一個分散式的結構中。根據應用程式,改變結構,包括跨伺服器同步會話狀態和複製資料庫事務等,這些艱巨的任務。根據不同的應用,改變結構可以包括跨伺服器的位置同步會話狀態和複製資料庫交易等艱巨任務。嘗試減少用戶和內容之間的距離,可以延遲,或從不通過,這是應用程式結構的步驟。
記住,最終用戶的80-90% 響應時間花費在下載所有頁面的組件:圖像、CSS、JS、Flash 等,這是提高性能的黃金法則。最好先分散你的靜態內容,如圖像、CSS、JS、Flash 等,而不是重新設計應用程式結構艱巨的工作開始。由於內容發佈網路,不僅大幅度減少了響應時間,而且簡化了應用程式。
一個 CDN 是一個處於多個位置的 Web 伺服器的集合,更有效地向用戶發送內容。選擇哪個伺服器發送內容給特定用戶通常是基於一個網路評估。例如,選擇最少的網路跳數或最快的響應時間。
一些大型的互聯網公司擁有自己的 CDN,而通過 CDN 服務提供商,如 Akamai Technologies, EdgeCast 或 level3,成本則很高。對於剛成立的公司和私人站點,一個 CDN 服務的成本可以讓人望而卻步,但當你越來越受關註,並全球化時,一個 CDN 是必需的,以便快速響應。以 Yahoo! 為例,他們把靜態內容從應用程式中移到 CDN(上面提到 CDN 服務提供商,以及他們自己的 CDN)上,提高了最終用戶 20% 以上的響應時間。使用 CDN 是一個只需要相對簡單地修改代碼,顯著改善站點速度的方法。
批註
客戶端訪問伺服器,要經過路由,是要計算代價的,最小跳數也好,最少響應時間也罷,所以將自己的網站內容部署在多個地理位置是有必要的。
添加 Expires 或 Cache – Control 頭
類別: server
此規則有兩個方面:
- 對於靜態組件:設置 Expires 頭為 "Never expire" 策略——“永不過期”;
- 對於動態組件:使用適當的 Cache – Control 頭,幫助瀏覽器有條件地發送請求。
隨著頁面越來越豐富,這意味著更多的 JS、CSS、圖像和 Flash。一個初次訪問頁面的用戶會發出很多 HTTP 請求,但是通過 Expires 頭,你可以使那些組件被瀏覽器緩存。在之後的頁面瀏覽,就避免了不必要的 HTTP 請求。Expires 頭經常用在圖像,但也可以用在包括 JS、CSS 和 Flash 所有組件。
瀏覽器(和代理)使用緩存來減少 HTTP 請求的次數和規模,使頁面載入速度更快。一個 Web 伺服器在 HTTP 響應中使用 Expires 頭,會告訴客戶端這個組件被緩存多長時間。下麵 Expires 頭告訴瀏覽器,這個響應直到 2011年4月15日 都是可靠的。
Expires: Thu, 15 Apr 2011 20:00:00 GMT
如果你的伺服器是 Apache,使用 ExpiresDefault 指令來設置相對於當前日期的過期時間。下麵例子 ExpiresDefault 指令設置過期時間為發出請求後的 10年。
ExpiresDefault "access plus 10 years"
記住,如果使用 Expires 頭,那麼當組件改變時,你必須改變組件的文件名。以 Yahoo 為例,常常使這步作為生成過程的一部分:一個版本號內置在組件文件名,如 yahoo_2.0.6.js。
使用 Expires 頭隻影響那些用戶已經瀏覽過的頁面。當用戶初次訪問,瀏覽器緩存為空時,不會影響 HTTP 請求的數量。因此,這種性能改善的影響取決於用戶多長時間會在 primed cache (primed cache 是已經包含頁面中的所有組件,它與 Empty Cache 相對)命中你的頁面。我們在 Yahoo 做了測試,發現在 primed cache 瀏覽頁面的頻率是 75-85 %。通過使用 Expires 頭,增加被瀏覽器緩存的組件數量,在接下來的瀏覽中可以重用,而無需通過用戶的網路連接發送任何位元組。
批註
根據 Yahoo 的統計,用戶從緩存中獲得頁面所有組件的頻率在 75-85 %,那麼我們很有必要告訴瀏覽器如何緩存頁面資源。比如圖像、CSS 這樣的靜態資源,就告訴瀏覽器永不過期。而對動態資源,則告訴瀏覽器,要有條件的請求,別重新請求。
Gzip 組件
類別: server
通過網路傳輸 HTTP 請求和響應所花費的時間,可以通過前端機制而得到顯著減少。事實上,最終用戶的寬頻速度、Internet 伺服器提供商、點對點交換接近程度等等,因素不是開發團隊能控制的,但是,還有其他影響響應時間的因素(這些是可以控制的)。壓縮,通過減小 HTTP 響應的大小,來減少響應時間。
從 HTTP/1.1 開始,Web 客戶端用 HTTP 請求中的 Accept – Encoding 頭來指示是否支持壓縮。
Accept-Encoding: gzip, deflate
如果 Web 伺服器在請求中看到這個頭,它可以利用客戶端列出的方法之一壓縮響應。Web服 務器通過響應的 Content – Encoding 頭通知客戶端。
Content-Encoding: gzip
Gzip 是目前最流行和最有效的壓縮方法。它是由 GNU 開發的項目,並通過 RFC 1952 標準化。你可能看過其他壓縮格式——deflate,但它的效率較差,不太流行。
Gzip 壓縮一般可以減少約 70% 的響應大小。目前大約有 90% 通過瀏覽器的互聯網流量,都聲稱支持Gzip,今天的互聯網流量約 90% 穿過聲稱支持 gzip 的瀏覽器。如果你使用 Apache,配置 Gzip 模塊取決於你的版本:Apache 1.3 使用mod_gzip ,而 Apache 2.x mod_deflate。
總所周知,瀏覽器和代理帶來的問題是,可能會導致瀏覽器期望的與它收到的壓縮內容不匹配。幸好,這種特殊情況隨著舊式瀏覽器使用的減少在減少。Apache 模塊會自動添加變化的響應頭來解決這個問題。
伺服器選擇什麼壓縮成 gzip,要根據文件類型,但通常很有限。大多數網站壓縮他們的 HTML 文件,腳本和 CSS 也很值得壓縮,但是很多站點錯過了這個機會。事實上,壓縮任何文本響應,包括 XML 和 JSON,都是值得的。圖像和 PDF 文件不應該被壓縮,因為它們已經被壓縮了。試圖壓縮他們,不僅浪費 CPU,還會潛在增加文件的大小。
用 Gzip 壓縮儘可能多的文件類型是一種減小頁面大小,加速用戶體驗的簡單方法。
批註
如果你瞭解 HTTP 協議,或是用相關網頁測試工具,那麼一定知道伺服器對客戶端都響應了些什麼。瀏覽器接收這些內容後,解析,並呈現給用戶。無論是接收 CSS 、腳本文件,還是圖像,那麼伺服器的響應的時候,將它們壓縮,再發送給客戶端,就是很符合邏輯的結果。
CSS 放在頁面頂部
類別:css
在研究 Yahoo! 的性能時,我們發現,把 CSS 放到 HEAD 標記使得頁面載入快了。這是因為,把 CSS 放在 HEAD 標記使得頁面逐漸地呈現。
關心性能的前端工程師期望一個頁面能逐漸載入。也就是說,我們希望瀏覽器儘快顯示內容。這對於擁有較多內容的頁面和網速較慢的用戶來說特別重要。給用戶返回可見的反饋的重要性,比如進度指示,已經做了很好的研究,並形成了正式文檔。在我們看來,HTML 頁面就是進度指示器。當瀏覽器逐漸載入頁面,頭部、導航條、頂部 logo 等等,對於等待頁面載入的用戶來說,都可以作為可見的反饋信息。這便從整體上改善了用戶體驗。
把CSS放在接近文檔底部的問題是,阻止在很多瀏覽器上逐漸呈現,包括 Internet Explorer。這些瀏覽器阻塞呈現是為了避免,如果式樣改變,那麼必須重繪頁面元素。用戶不得不面對一個空白頁面。
HTML 規範清楚地指出 CSS 要包含在頁面的 HEAD 區域:“與 A 不同,<link />只能出現在文檔的 HEAD 區域,儘管它可以出現很多次。”無論是白屏,還是出現沒有式樣的內容,都是不值得的。最好的解決方法就是按照 HTML 規範,在文檔的 HEAD 裡加載 CSS。
批註
有種情況,你一定見過:當很多人下載電影,占了帶寬,網頁要麼打不開,要麼打開了,有內容沒式樣。瀏覽器解析伺服器發過來的頁面,總是有個順序問題的。比如,在沒獲得 CSS 文件前,就準備呈現頁面是沒有意義的。
腳本放在頁面底部
類別:javascript
腳本帶來的問題是它阻止了並行下載。HTTP/1.1 規範建議,瀏覽器並行下載,每個主機不能超過 2 個組件。如果你的圖片放在多個主機上,那麼你可以從每個主機並行下載兩個資源。然而,當下載腳本時,瀏覽器就不會下載其他資源,即便資源位於不同的主機。
在某些情況下,把腳本放在底部不太容易。比如,如果腳本使用 document.write 向頁面插入內容,它就不能被往下移。這裡還會有作用域問題。很多情況下,都會遇到這方面問題。
一個經常用的方法是使用延遲腳本(deferred scripts)。DEFER 屬性指示,腳本不包含 document.write,告訴瀏覽器可以繼續呈現。不幸的是,Firefox 並不支持 DEFER 屬性。在 Internet Explorer 中,腳本可以被延遲,但效果可能不像期望的那樣。如果腳本可以被延遲,那麼它就可以被移到頁面的底部,這將使頁面載入加快。
批註
這點很容易理解。如果不考慮上面提到 document.write 情況,那麼絕大多數腳本,要麼是創建頁面標記(控制項),要麼修改頁面標記,無論從那個角度講,腳本最後執行都是最合適的。
比如,你在 HEAD 里的腳本使用了頁面的元素,那估計腳本會報錯,因為元素那時還沒有被創建。所以,CSS、JS 等頁面資源,別亂放。
現在的 Ajax 框架,比如 jQuery 的 ready 方法,Ext.Net 的 Ext.onReady 方法等都是處於這個目的。
避免 CSS 表達式
類別:css
CSS 表達式是動態設置 CSS 屬性強大(而危險)的方法。從 Internet Explorer 5 開始支持 CSS 表達式,但從 IE 8 開始被廢棄。下麵例子,使用 CSS 表達式實現每隔一個小時設置一次背景顏色:
background-color: expression( (new Date()).getHours()%2 ? "#B8D4FF" : "#F08A00" );
如上所示,CSS 表達式使用了一個 JavaScript 表達式。CSS 屬性根據 JavaScript 表達式的計算結果來設置。表達式在其它瀏覽器中不起作用,因此,在跨瀏覽器的設計中,針對 Internet Explorer 設置屬性會比較有用。
CSS 表達式的問題在於它的計算頻率比我們想象得多。不僅在頁面顯示和縮放時,也在頁面滾動,甚至在界面上移動滑鼠,都會重新計算。給 CSS 表達式增加一個計數器可以讓我們跟蹤表達式何時計算以及計算頻率。隨便在頁面里移動滑鼠都可以輕鬆達到 10000 次以上。
減少 CSS 表達式計算次數的一個方法是使用一次性的表達式。當第一次計算表達式時,它將結果賦值給式樣屬性,並用這個值代替 CSS 表達式。如果樣式屬性必須在頁面周期內動態地改變,那麼一個可行的方法是使用事件處理,而不是 CSS 表達式。如果你必須使用 CSS 表達式,那麼一定要記住,它們可能要運行成千上萬次,有可能會影響以哦面性能。
批註
雖然有用,但存在的問題很突出、更要命。所以,還是不要使用的好。
使用外部 JavaScript 和 CSS
類別:javascript,css
很多性能規則都是如何管理外部文件?但在你思考這些問題前,你應該問一個更基本的問題:JavaScript 和 CSS 是應該放在外部文件中,還是應該內嵌在頁面里?
實際中,使用外部文件通常可以提高頁面速度,因為,JavaScript 和 CSS 文件可以被瀏覽器緩存。而內嵌在 HTML 文檔中的 JavaScript 和 CSS 會在每次請求 HTML 文檔時被重新下載。這雖然減少了所需的 HTTP 請求次數,卻增加了 HTML 文檔的大小。而另一方面,如果 JavaScript 和 CSS 在外部文件,並被瀏覽器緩存,那麼在沒有增加 HTTP 請求次數情況下,減少 HTML 文檔的大小。
然而,關鍵問題是,被緩存的外部 JavaScript 和 CSS 組件的頻率,與請求 HTML 文檔的次數有關。儘管很難量化,但還是有很多指標來測量它。如果用戶在每次會話中瀏覽你網站的多個頁面,而這些頁面重用了 腳本 和CSS,那麼使用可以被瀏覽器緩存的外部文件將會帶來很大好處。
很多的網站沒能建立起這些指標。對這些網站,一般來說,最好的解決方法是把 JavaScript 和 CSS 作為外部文件來部署。適合採用內嵌代碼的唯一例外是網站的主頁,如 Yahoo!'s front page 和 My Yahoo!。主頁在每次會話中很少被瀏覽,你會發現,在主頁嵌入 JavaScript 和 CSS 對最終用戶的響應時間更快。
對於擁有較大訪問量的首頁,有一種技術可以平衡嵌入代碼帶來的減少 HTTP 請求,與使用外部文件帶來緩存的好處。其中一個技術就是在首頁中嵌入 JavaScript 和 CSS,但完成載入後,動態下載外部文件。接下來的頁面就會使用已經被瀏覽器緩存的外部文件。
批註
既然只有外部文件才可以被瀏覽器緩存,那麼何樂不為呢。而且嵌入到頁面腳本代碼,也不好維護,同時,會增大頁面大小。
減少 DNS 查詢
類別:content
功能變數名稱解析系統(DNS)提供功能變數名稱和 IP 的映射,就像電話本映射人名與他們的電話號碼一樣。當你在瀏覽器地址欄鍵入 www.dudo.org 時,DNS 解析會返回給瀏覽器對應的 IP。DNS 解析是有時間代價的。一般情況下,查找給定功能變數名稱對應的 IP,需要 20 到 120 毫秒。在這個過程中,瀏覽器不會下載任何東西,直到 DNS 查詢完畢。
緩存 DNS 查詢可以改善性能。DNS 緩存發生在要一個特定的緩存伺服器,由用戶的 ISP 或本地網路維護,但也會緩存在用戶自己的機器上。DNS 信息保存在操作系統的 DNS 緩存中(微軟 Windows 操作系統的 "DNS Client Service")。大多數瀏覽器都有自己的緩存,它獨立於操作系統之外。只要瀏覽器在自己的緩存維護一個 DNS 信息,在一次請求中就不會受到操作系統的影響。
預設情況下,Internet Explorer 的 DNS 緩存為 30 分鐘,由註冊表的 DnsCacheTimeout 規定。Firefox 的 DNS 緩存為 1 分鐘,由配置文件 network.dnsCacheExpiration 控制(Fasterfox 為 1 小時)。
當客戶端的 DNS 緩存為空(瀏覽器和操作系統的 DNS 緩存都為空),DNS 查詢的次數等於頁面中主機的數量。這包括頁面中的 URL、圖像、JS、CSS、Flash 等使用的主機。減少唯一主機名的數量就可以減少 DNS 查詢的次數。
但減少唯一主機名的數量潛在地減少了並行下載的數量。雖然避免 DNS 查詢次數節省了響應時間,但是減少並行下載卻增加了響應時間。我的原則是,把頁面組件分割在 2 個到 4 個主機之間。這樣就是在減少 DNS 查詢次數與較高的並行下載之間獲得了權衡。
批註
Yahoo 還真是,任何一個環節都不放過。但是試想一下,DNS 查詢在 20-120 毫秒之間,這個時間里相當於可以從伺服器上下載 1-2 個幾十k的資源,也許是 CSS 文件,也許是腳本文件,也許是圖片,所以減少 DNS 查詢還是很有必要的。
精簡 JavaScript 和 CSS
類別:javascript,css
“精簡”是工程實踐的結論,從代碼中去掉不必要的字元,以減少文件大小,從而節省載入時間。去掉代碼的所有註釋、空白字元(包括空格、換行、tab)。在 JavaScript 中,這會提高響應時間,因為減少了下載文件的大小。精簡 JavaScript 代碼最流行、最廣泛的兩個工具是 JSMin 和 YUI Compressor。YUI Compressor 還可用於精簡 CSS。
“混淆”是另外一種可用於源代碼優化的方法。該方法要比精簡複雜一些,並且混淆很可能產生 BUG。在對美國前 10 名的網站調查中發現,“精簡”可以縮小源代碼 21% 的體積,而“混淆”可以達到 25%。儘管“混淆”可以更大程度減少代碼大小,但精簡 JavaScript 的風險更小。
除了精簡外部 JavaScript 和 CSS,內嵌的 <script> 和 <style> 代碼塊也可以(應該)精簡。即使使用 Gzip 壓縮過的 JavaScript 和 CSS,“精簡”文件仍然可以減少 5% 以上的大小。隨著使用的 JavaScript 和 CSS 大小的增加,精簡代碼將會獲得更大的益處。
批註
無論是 JavaScript,還是 CSS,所有頁面資源都是要從伺服器下載的,它們當然是越小越好。所以往往,三方框架,比如 jQuery 等,都會提供腳本文件的正式版本(精簡過的,如果你打開看一下,密密麻麻一坨)和它們的debug版本。
避免重定向
類別:content
“重定向”是通過 HTTP 狀態碼 301 和 302 完成的。下麵是一個 301 響應的 HTTP 頭:
HTTP/1.1 301 Moved Permanently
Location: http://example.com/newuri
Content-Type: text/html
瀏覽器會自動地把用戶定向到 Location 中指定的 URL。所有重定向需要的信息位於頭中。響應的內容可以是空的。無論是 301 響應,還是 302,它們都不會被緩存,除非增加一個額外的頭選項,如 Expires 或者 Cache-Control,來指定可以被緩存。meta 標記和 JavaScript 是另一個實現重定向的方法,但是如果你必須要跳轉,那最好的方法是使用標準的 3XX HTTP 狀態碼,這主要是為了確保“後退”按鈕可以正確地使用。
需要記住的主要事情是,重定向會降低用戶體驗。在用戶和 HTML 文檔之間插入一個跳轉,會延遲頁面中所有元素的呈現,因為,在 HTML 文件被載入前,頁面的任何東西都不會被呈現,組件也不會被下載。
經常發生,最浪費的重定向,也經常被網頁開發者忽略。那就是,URL 缺少斜杠(/),可本應該有。例如,訪問 http://astrology.yahoo.com/astrology 會導致 301 響應代碼的跳轉,連接應該是 http://astrology.yahoo.com/astrology/ (註意末尾的斜杠)。在 Apache 中,可以使用 Alias 或者 mod_rewrite,或者 DirectorySlash 檢測來避免。
另一個經常使用重定向的情況是,把舊網站連接到新網站。其他情況如連接網站的不同部分,或根據一定條件(瀏覽器類型、用戶帳號類型等)來引導用戶。使用重定向連接兩個網站很簡單,只需要很少的代碼。儘管該方法對開發者來說,減少了複雜度,但是缺降低了用戶體驗。一個可替代的方法是,如果兩者在同一臺伺服器上,那麼可以使用 Alias 和 mod_rewrite。如果是因為功能變數名稱改變而使用重定向,那麼可以結合 Alias 或 mod_rewrite,使用 CNAME(創建從一個功能變數名稱指向另外一個功能變數名稱的 DNS 記錄)。
批註
這個是有切身體會的,尤其是使用客戶端重定向(瀏覽器重定向)時,頁面跳轉的速度實在是有點慢。
刪除重覆腳本
類別:javascript
在一個頁面中引用同一個 JavaScript 文件兩次會影響性能。這種情況可能並不常見。在對於美國前 10 名網站的調查中顯示,其中的兩家存在重覆引用腳本的情況。有兩個主要因素增加一個頁面中重覆引用腳本的幾率:團隊規模和腳本數量。當發生這種情況時,重覆引用腳本會創建不必要的 HTTP 請求,以及延緩腳本執行,從而損害性能。
這種不必要的 HTTP 請求發生在 Internet Explorer,而不會在 Firefox。在 Internet Explorer 中,如果包含引用一個外部腳本兩次,並且它還不可緩存,那麼在頁面載入期間,它會產生兩次 HTTP 請求。即使腳本可被緩存,當頁面重新載入時,也會產生額外的 HTTP 請求。
除了產生多餘的 HTTP 請求,多次檢查腳本也會浪費時間。無論腳本是否可被緩存,在 Internet Explorer 和 Firefox 中都會發生多餘的腳本執行。
一個避免意外地引用同一個腳本兩次的方法是,在你的系統中,開發腳本管理模塊。在 HTML 頁面中包含腳本的常見方法是使用 <script > 標記:
<script type="text/javascript" src="menu_1.0.17.js"></script>
而在 PHP 中,通過創建名為 insertScript 方法:
<?php insertScript("menu.js") ?>
為了防止多次插入同一個腳本,該方法可以解決其他腳本問題,例如依賴檢查,為腳本文件名添加版本號,以便使用 Expire 頭。
配置 ETags
類別:server
“實體標記(Entity tags,ETags)”是 Web 伺服器和瀏覽器用於確定,瀏覽器緩存中的組件與伺服器的一個原始內容是否匹配的一種機制。“實體”就是“組件”:圖像、腳本、CSS 等。添加 ETags 會提供一種驗證實體的機制,這比最後修改日期 last-modified date 更加靈活。一個 ETags 是一個唯一標識一個特定版本組件的字元串。字元串必須用雙引號括起來。伺服器通過 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 HTTP 狀態碼,減少了 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。即使一個給定的文件在多個伺服器的相同目錄,且具有相同的大小、許可權、時間戳等,但它的 inode 仍然是不同的。.
IIS 5.0 和 IIS 6.0 的 ETags 具有類似的問題。IIS 的 ETag 格式是 Filetimestamp:ChangeNumber。ChangeNumber 是一個計數器,用來跟蹤 IIS 配置的改變。不同的是,ChangeNumber 在一個 Web 站點的所有 IIS 都是相同的。
對於完全相同的組件,在不同的伺服器上,由 Apache 和 IIS 產生的 ETags 不同。如果 ETags 不匹配,那麼用戶不會收到一個小而快的 304 響應;而是,會得到一個正常的 200 響應,並下載全部內容。如果你的網站只在一臺伺服器上,那不會有問題。但是如果你的網站架設在多個伺服器,並且使用 Apache,或預設 ETag 配置的 IIS,那麼用戶獲得頁面會相對較慢,你的伺服器負載較高,帶寬消耗較大,代理不會有效地緩存你的內容。即使你的組件具有 Expires 頭,無論用戶何時點擊“重載”或者“刷新”,都會發送一個有條件的 GET 請求。
如果你沒有利用 ETag 提供的靈活驗證模式,那麼最好把 ETag 刪掉。Last-Modified 頭是基於組件時間戳來驗證。刪掉 ETag 會在響應和接下來的請求中減少 HTTP 頭的大小。Microsoft Support article 文檔描述如何刪掉 ETag。在 Apache 中,只需要在配置文件中簡單添加下麵一行代碼就可以:
FileETag none
批註
這種為資源的生成唯一標識的做法很常見。如果文件有改動,那麼唯一標識一定不同。
使 Ajax 可緩存
類別:content
Ajax 被提到的一個好處是,由於它從伺服器非同步請求信息,能為用戶提供即時反饋。然而,使用 Ajax 並不能保證用戶不用等待那些返回的非同步 JavaScript 和 XML 響應。在很多應用中,用戶是否等待取決於如何使用 Ajax。例如,在一個基於 Web 的 Email 客戶端中,用戶必須等待 Ajax 請求的結果,返回符合查找條件的郵件信息。很重要的一點,“非同步”並不意味著“即時”。
為了提高性能,優化 Ajax 響應很重要。提高 Ajxa 性能的最重要的方法是使響應可緩存,如“添加 Expires 和 Cache-Control 頭”小節描述的。其他幾條規則也適用於 Ajax:
- Gzip 組件
- 減少 DNS 查詢
- 減小 JavaScript
- 避免重定向
- 配置 ETags
讓我們看一個例子:一個 Web2.0 Email 客戶端會使用 Ajax 自動下載該用戶的地址簿。如果用戶在上次使用 Email Web 應用程式後,沒有修改地址簿,那麼,如果用 Expire 或者 Cacke-Control 頭設置 Ajax 可緩存,就可以直接從緩存中讀取地址薄。必須告訴瀏覽器,是使用緩存中的地址薄,還是發送一個新的請求。這可以通過給地址薄的 Ajax URL 添加一個時間戳,指示用戶最後修改地址簿的時間,例如,&t=11900241612。如果地址薄在上次讀取後沒有被修改過,那麼時間戳不變,從瀏覽器的緩存中讀取地址簿,以減少額外的 HTTP 請求。如果用戶修改了地址薄,那麼時間戳會確保新的 URL 與緩存中的響應不匹配,瀏覽器將請求新的地址簿。
即便你的 Ajxa 響應是動態生成的,哪怕它只應用於一個用戶,那也應該把它們緩存起來。這樣可以使你的 Web2.0 應用程式響應速度更快。
儘早強制地發送緩衝給客戶端
類別:server
當用戶請求一個頁面時,無論如何後端都會花 200 到 500 毫秒,以便組織 HTML 頁面。期間,瀏覽器會一直是空閑的,直到數據到達。在 PHP 中,你可以使用 flush() 方法,它允許你把部分 HTML 響應發送給瀏覽器,這樣,瀏覽器就可以開始獲取組件,同時後臺處理 HTML 頁面的剩餘部分。這樣做好處在後端繁忙,而前端空閑時最明顯。
考慮強制發送的一個最好的地方是 HEAD 後,因為 HTML 頭通常最容易生成,讓你可以包含任何 CSS 和 JavaScript 文件,以便瀏覽器在開始並行下載的同時,後端仍然在處理。 例子:
... <!-- css, js -->
</head>
<?php flush(); ?>
<body>
... <!-- content -->
Yahoo! search 率先進行了研究,真實的用戶測試表明瞭該技術的好處。
批註
不用等到全部內容都有了,再發給客戶端,這期間用戶都看不到。有的話,就立刻發出去,讓頁面“慢慢”呈現出來。
使用 GET 發送 Ajax 請求
類別:server
Yahoo!Mail 團隊發現,當使用 XMLHttpRequest 時,瀏覽器中 POST 的實現是兩步過程:首先發送頭,然後才發送數據。這樣,使用 GET 最好,因為,它只發送一個 TCP 包(除非你有很多 cookie)。IE URL 的最大長度為 2K,因此,如果你要發送一個超過 2K 的數據,那就不能使用 GET。
一個有趣的副作用是,沒有真正發送任何數據的 POST 的行為有點像 GET。根據 HTTP 規範,GET 意味著“檢索”數據,因此,當你只是查詢數據時,GET 更加有意義(從語意上也是如此),相反,發送併在服務端保存數據時使用 POST。
延遲載入組件
類別:content
你可以仔細看一下你的網頁,問問自己“哪些內容是頁面初次呈現所必需的?”。剩下的內容和組件可以稍後載入。
JavaScript 是一個理想的選擇,按照 onload 事件分成兩部分,之前和之後。例如,如果你有用於完成拖拽效果的 JavaScript 和庫,那麼它們可以稍後載入,因為頁面的拖拽元素是在頁面初始呈現後才發生的。其他稍後載入的選擇包括隱藏的內容(這些內容是用戶操作後才出現的),以及處於摺疊的圖像。
幫你減輕該工作的工具:YUI Image Loader 可以讓你推遲載入摺疊部分的圖片。YUI Get utility 是包含 JS 和 CSS 的便捷方法。例如,你可以用 Firebug 的 Net 選項卡看一下 Yahoo! Home Page。
當性能目標體現在 Web 開發的最佳實踐時,就會有很好的效果。這種情況下,通過漸進增強(progressive enhancement )的思想告訴我們,在支持 JavaScript 的情況下,JavaScript 可以改進用戶體驗,但是必須確保頁面沒有JavaScript 也可以正常工作。因此,在確保頁面運行正常後,用延遲載入腳本來增強頁面,比如拖拽和動畫腳本。
批註
剛開始只載入最基本,暫時不需要的組件,就不用載入。遵循“漸進增強”原則。
預載入組件
類別:content
“預載入”和“延遲載入”看似相反,但實際上“預載入”是為了實現另外一種目標。通過預載入,你可以利用瀏覽器空閑的時間,請求將來需要的組件(如圖像、CSS 和腳本)。使用這種方法,當用戶要訪問下一個頁面時,頁面的大部分組件都已經在緩存中了,這會打打改善用戶載入頁面的速度。
下麵是幾種“預載入”的方法:
- 無條件的預載入:只要觸發 onload 事件,你就直接獲取額外的組件。以 Google.com 為例,看一下合成的圖像是如何在 onload 中載入的。合成中的圖像在 google.com 主頁並不需要,但在一個“連續”的檢索結果頁面中是需要的。
- 有條件的預載入:根據用戶的操作,你可以推測出用戶接下來會做什麼,進行相應的預載入。在 search.yahoo.com 中,你可以看到在你在文本框輸入後,如何請求額外的組件。
- 期望的預載入:在重新設計前,應先考慮預載入。當重新設計後,你經常能聽到:“新的站點和不錯,但比之前慢了”。部分問題在於,用戶在完全緩存里訪問你的之前的站點,而新的站點一直是空的緩存。因此,即便要重新設計,你也要通過預載入減輕這種副作用。舊站點使用瀏覽器的空閑時間,請求新站點使用的圖像和腳本。
批註
最明顯的情況是,“翻轉”效果。
減少 DOM 元素數量
類別:content
一個複雜的頁面意味下載更多的數據,也就意味著 JavaScript 訪問 DOM 會變慢。例如,當你添加一個事件處理時,遍歷頁面的 500 或 5000 個元素是不一樣的。
大量的 DOM 元素是一個徵兆,它意味著,可以使用頁面標記,而無需刪除內容。你是否佈局而採用內置表格?是否僅僅為了自適應而使用很多 DIV?也許有一個更好,更符合語義的方法。
用 YUI CSS utilities 來佈局很方便:grids.css 可以幫你整體佈局,font.css 和 reset.css 可以幫助你移除瀏覽器預設的格式。這提供了一個重新審視和思考標記的機會,例如,只有當在語義上說得通時才使用<div>,而不是因為它能呈現一個新行才使用。
DOM 元素數量很容易計算,只需要在 Firebug 控制台內輸入:
document.getElementsByTagName('*').length
那麼多少個 DOM 元素算多?可以對比一下好的頁面。比如 Yahoo! Home Page 是一個內容很多的頁面,但它只有 700 個元素(HTML 標簽)。
批註
頁面好看與頁面複雜總是很矛盾。夠炫的頁面往往都很複雜,元素很多,元素很多的話,JavaScript 訪問起來就會變慢。
根據功能變數名稱分割組件
類別:content
分割組件可以使你最大限度地並行下載。由於 DNS 查找的影響,確保你使用的功能變數名稱在2到4個之間。例如,你可以把 HTML 和動態內容放在 www.example.org 上,而把分割的靜態組件(圖片、腳本、CSS)放在 statics1.example.org 和 statics.example.org。
你可以在 Tenni Theurer 和 Patty Chi 合寫的文章 "Maximizing Parallel Downloads in the Carpool Lane" 找到更多相關信息。
批註
既然在 HTML 規範中規定資源是可以並行下載的,那麼我們當然可以將網站的資源分別存在不同的地方。
最小化 iframe 數量
類別:content
ifrmae 元素可以在父文檔中插入一個新的 HTML 文檔。瞭解 iframe 如何工作,才能有效地使用它。
<iframe> 優點:
- 幫助載入緩慢的三方部件和廣告等
- Security sandbox
- 並行下載腳本
<iframe> 缺點:
- 即便載入是空的,也有代價
- 會阻止頁面載入
- 沒有語意
不要出現 404 錯誤
類別:content
HTTP 請求很昂貴。因此,發送一個 HTTP 請求,卻獲得一個無用的響應(如,404 Not Found)是完全沒必要的,它只會降低用戶體驗,而不會有一點好處。
有些站點把 404 錯誤響應頁面改為“你是不是要找***?”,這雖然改進了用戶體驗,但卻浪費了伺服器資源(像資料庫等)。最糟糕的情況是指向一個外部 JavaScript 鏈接,返回 404 錯誤。首先,這個下載會阻塞並行下載。其次,瀏覽器會試圖解析 404 響應的內容,就像它是 JavaScript 代碼,嘗試在裡邊查找有用的東西。
減小 Cookie 大小
類別:cookie
使用 HTTP coockie 有很多原因,比如認證(authentication )和個性化。在 Web 伺服器與瀏覽器之間,通過 HTTP 頭來交換 coockie 信息。儘可能維持 coockie 的大小,以減少對用戶響應時間的影響,很重要。
有關更多信息,可以查看 Tenni Theurer 和 Patty Chi 的文章 "When the Cookie Crumbles"。該文內容的研究包括:
- 去掉不必要的 coockie
- 儘可能維持 coockie 的大小,以減少對用戶響應時間的影響
- 註意在適當功能變數名稱級別上設置 coockie,以便其他子域不會受到影響
- 適當設置的過期時間。刪除 cookie 較早的過期時間或沒有,能改善用戶的響應時間。
對組件使用無 coockie 功能變數名稱
類別:cookie
當瀏覽器請求一個靜態圖像,並且隨請求發送 coockie 時,伺服器並不會使用這些 coockie。因此,毫無疑問,它們(coockie )只會產生網路流量。你應該確保請求靜態組件時,請求中不帶 cookie。創建一個域,把你所有的靜態組件放在該子域。
如果你的功能變數名稱是 ww.example.org,那麼你可以把靜態組件放在 static.example.org 上。然而,如果你已經在頂級域 example.org 設置了 coockie,而不是在 www.example.org 上,那麼,所有對 static.example.org 的請求都將包含 coockie。在這種情況下,你可以購買一個新功能變數名稱,存放你的靜態組件,並讓這個功能變數名稱無 coockie。Yahoo! 使用的是 ymig.com,YouTube 使用的是 ytimg.com,Amazon 使用 images-anazon.com 等。
使用無 coockie 功能變數名稱存放靜態組件的另外一個好處是,一些代理(伺服器)可能會拒絕緩存帶 coockie 請求的組件。相關建議是,如果你想確定是用 example.org 作為你的主頁,還是 www.example.org,那麼,你可以考慮 coockie 帶來的影響。沒有 www 的會把 coockie 設置到 *.example.org 的所有域,這樣你就別無選擇了。因此,出於性能的考慮,最好使用 www 子功能變數名稱,併在它上設置 coockie。
最小化 DOM 訪問
類別:javascript
用 JavaScript 訪問 DOM 元素比較慢,因此為了更好響應頁面,你應該:
- 緩存已經訪問過的原始
- 離線更新完節點後,再將它們添加到文檔樹中
- 避免使用 JavaScript 來自適應佈局
有關更多信息,請查看 Julien Lecomte 的文章 "High Performance Ajax Applications"。
開發智能事件處理程式
類別:javascript
有時,頁面反應遲鈍,這是因為,太多綁定到 DOM 樹元素的事件處理,並且被頻繁執行。這就是為什麼使用事件托管(event delegation)。如果你在一個 div 中有 10 個按鈕,那麼你只需在 div 上綁定一個事件處理(利用委托),而不是為每個按鈕。事件冒泡時,你可以捕捉到該事件,並判斷出是哪個事件發出的。
你也不用為了操作 DOM 樹而等待 onload 事件。通常,你所需要就是訪問DOM 樹中可用的元素。你也不必等待所有圖像都載入完畢。不用 onload,DOMContentLoaded 是可以考慮的事件,但在所有瀏覽器都支持它之前,你可使用 YUI Event工具,它有一個 onAvailable 方法。
有關更多信息,參看 Julien Lecomte 的文章 "High Performance Ajax Applications"。
批註
換句話說,通過事件委托,我們可以為 10 個按鈕只使用一個事件,而不是 10 個按鈕搞 10 個事件處理。比如,增刪改三個按鈕,可以用一個事件來弄,事件觸發時,可以知道當前操作的是什麼。
用 <link> 代替 @import
類別:css
前面的最佳實踐提到,CSS 應該放在頁面頂部,以便漸進呈現。
在 IE 中,@import 的行為相當於把 <link> 放在頁面頂部,因此最好不要使用它。
避免使用濾鏡
類別:css
IE 獨有的 AlphaImageLoader 濾鏡旨在修複 7.0 以下版本的半透明真彩色 PNG 圖像問題。該濾鏡的問題是,當圖像正被下載時,它會阻塞呈現,並凍結瀏覽器。濾鏡也會增加記憶體消耗,並被應用到每個元素,而不是每個圖像,因此,濾鏡的問題是多方面的。
最好的方法是避免完全使用 AlphaImageLoader,而是使用 PNG8,它能在 IE 中很好地工作。如果你確實需要使用 AlphaImageLoader,那應該使用 hack_filter,不會影響到 IE7 以上版本的用戶。
優化圖像
類別:images
設計人員為頁面創建圖像後,向 Web 伺服器上傳圖像前,你可以試著做以下幾件事:
- 檢查 GIF 圖像,看下圖像中的顏色數量是否與調色板一致。使用 imagemagick 可以很容易檢查:
identify -verbose image.gif
如果你發現圖像中只用了 4 種顏色,而調色板中是 256 色,那麼這張圖片就改進的空間。
- 嘗試把 GIF 格式轉換成 PNG 格式,看看是否節省了空間。多數情況下會。早先由於瀏覽器支持有限,開發者不太願意使用 PNG 格式,但現在已經成為過去。唯一一個問題是真彩 PNG 格式的 alpha 通道半透明問題,不過 GIF 也不是真彩的,並且不支持任何半透明。因此,GIF 能做到的,調色板 PNG(PNG8)同樣也能做到(除了動畫)。下麵一條簡單的 imagemagick 命令可以把 GIF 安全地完全轉換成 PNG:
convert image.gif image.png
- 在你所有的 PNG 圖像上,運行 pngcrush(或者其它 PNG 優化工具)。例如:
pngcrush image.png -rem alla -reduce -brute result.png
- 在你所有的 JPEG 圖像上運行 jpegtran。這個工具可以對 JPEG 中出現的鋸齒等做無損操作,同時,它還可以用於優化和清除圖像中的註釋,以及其它無用信息(如 EXIF 信息):
jpegtran -copy none -optimize -perfect src.jpg dest.jpg
優化 CSS Sprites
類別:images
- 通常,在 Sprite 中水平排列圖像比垂直排列的文件要小。
- 在一個 Sprite 中合併結合相似的顏色會幫助你保持較低的色彩數,理想狀態是 256 色,以適應一個 PNG8。
- "Be mobile-friendly",並且不要在一個 Sprite 中的圖像之間留下較大的空隙。這不會影響文件的大小,但對於迫切需要解壓縮圖像到一個像素地圖上的用戶來說,需要更少的的記憶體。100x100 的圖像是1 萬個像素,而 1000X1000 是 100 個萬像素。
不要在 HTML 中縮放圖像
類別:images
不要使用比你實際需要的大的圖像。因為你可以設置長寬。如果你需要:
<img width="100" height="100" src="mycat.jpg" alt="My Cat" />
那麼你的圖像(mycat.jpg)就應該是 100x100 像素,而不是把一個 500x500 像素的圖片縮小來用。
批註
使用多大的圖像,就做多大的圖像。你可能見過,往往一個內容,會有不同像素大小的版本,比如工具欄按鈕圖標,同時做 16×16 像素,和 32×32 像素的。
favicon.ico 要小且可緩存
類別:images
favicon.ico 是一個放在你伺服器根位置的圖像。這是個必需的邪惡,因為,即使你不關心它,瀏覽器仍然會請求它,因此,最好不要用 404 響應。此外,由於位於同一個伺服器,每次發送 Cookie 都會請求它。這個圖像也會幹擾下載隊列,例如,在 IE 中,當你在 onload 請求一個額外的組件時,favicon 將在這些額外組件前被下載。
因此,為了減輕 favicon.ico 弊端,應確保:
- favicon 要小,最好在 1K 以下。
- 設置你感覺合適的 Expires 頭(若你想改變它,但你不能重新命名它)。你可以把 Expires 頭設置為未來幾個月。你可以檢查當前 favicon.ico 的最後修改日期,以便做決定。
ImageMagick 可以幫助你創建小的 favicons(網站圖例)。
批註
千萬別忘了 favicon.ico。這是瀏覽器預設下載的資源。
保持組件 25K 以下
類別:mobile
這個限制與一個事實有關,iPhone 不能緩存操作 25K 大小的組件。註意,這是未壓縮的大小。這個小是很重要的,因為單獨 gzip 完全不夠。
有關更多信息,參考 Wayne Shea 和 Tenni Theurer 的文章 "Performance Research, Part 5: iPhone Cacheability - Making it Stick"。
把組件打包到一個 Multipart 文檔
類別:mobile
把組件打包到一個 multipart 文檔很像一個帶附件的email,它能使你在一個 HTTP 請求中獲取多個組件(切記:HTTP 請求很昂貴)。當你使用這個技術時,首先要確定用戶代理是否支持(iPhone 不支持)。
避免圖片 src 屬性為空
類別:server
img src 屬性為空的經常發生。它有兩種形式:
- 直接 HTML 標記創建
<img src="">
- JavaScript 代碼創建
var img = new Image();
img.src = "";
這兩種形式效果相同:瀏覽器會向伺服器發出另一個請求。
- Internet Explorer 向頁面所在的目錄,發出一個請求。
- Safari 和 Chrome 向實際頁面自身,發出一個請求。
- Firefox 3 和更早版本的行為與 Safari 和 Chrome 相同,但其 3.5 版本解決了這個問題[bug 444931],不再發送請求。
- Opera 當遇到img src 屬性為空時,什麼都不做。
為什麼這個行為很糟?
- 發送大量不期望的流量,會使伺服器癱瘓,尤其是每天有百萬瀏覽的頁面。
- 浪費伺服器的處理周期,卻產生一個永遠不會被查看的頁面。
- 可能會破壞用戶數據。如果你通過 cookie或其他方式追蹤請求中的狀態,那麼會有損壞數據的可能性。即使圖像請求沒有返回圖像,瀏覽器也會讀取和接收所有的頭,包括所有 cookies。當餘下的響應被丟棄,損害可能已經產生。
這種行為的根本原因是在瀏覽器中完成 URI 解析的方式。這種行為定義在 RFC 3986 - Uniform Resource Identifiers。當遇到一個空字元串時就作為 URI,它被看作是一個相對的 URI,並根據 5.2 節定義的演算法解決。在 5.4 節 列出了空字元串的例子。Firefox、Safari 和 Chrome 都按規範正確解析字元串,而 Internet Explorer 沒有,還遵循早期的規範 RFC 2396 - Uniform Resource Identifiers(已由 RFC 3986 代替)。因此,技術上,瀏覽器做了它們被期望做的事,來解析相對 URI。空字元串顯然是個意外。
HTML5 在 4.8.2 節添加一個關於標記的 src 屬性的描述,指示瀏覽器不要發出額外的請求:
src 屬性必須存在,並且必須包含一個有效的 URL,它引用一個非互動式的、可選的、可有動畫的圖像源,不能是頁面或腳本。如果元素的基 URI 與文檔地址相同,那麼 src 屬性必須不能為空。
慶幸的是,之後的瀏覽器不會存在這個問題。而不幸的是,沒有對 <script src=""> 和 <link href=""> 的規定。可能還是有時間調整以確保瀏覽器執行此行為。
該規則得到 Yahoo!'s JavaScript guru Nicolas C. Zakas 的支持。有關更多信息,參考 "Empty image src can destroy your site"。