文章是高性能網站建設指南(Steve Souders)
的總結。
性能黃金法則:
只有10%~20%的最終用戶響應時間花在了下載HTML文檔上。其余的80%~90%時間花在了下載頁面中的所有組件上。
在這之后是14條提升性能的法則。這些規則按照常規的優先級順序列出。特定的規則,適用性可能不同。所以不同的網站需要合理的選取規則。
圖片地圖允許你在一張圖片上關聯多個URL。想必多張圖片(多個URL)而言,一張圖片只有一個HTTP請求。
圖片地圖實現方法有兩種,一種是服務器端圖片地圖,一種是客戶端圖片地圖。
服務器端圖片地圖是將所有點擊提交到同一個URL,向其傳遞x、y坐標。web應用程序將坐標映射為適當的操作。
客戶端圖片地圖是通過map
標簽。下面是w3c的示例:
客戶端圖片地圖靈敏度太差,只適合矩形,而且坐標易出錯。(我是沒用過)
一句話概括就是將很多張小圖片通過ps放到一張圖片上。這樣通過設置背景位置(background-position)在不同位置顯示合適的圖片。
同樣,使用雪碧圖也可以減少大量HTTP請求。
另一個令人驚奇的優點是,它還降低了下載量。很多人會認為合并后的圖片比分離的圖片的總和要大,因為合并后的圖片中包含有附加的空白區域。實際上,合并后的圖片會比分離后的圖片的總和要小,這是因為它降低了圖片自身的開銷(顏色表、格式信息,等等)。(對圖片要求不高,我使用iconfont)
通過使用data:URL模式可以在web頁面中包含圖片但無需任何額外的HTTP請求。
data:<mediatype>[;base64],<data>如:
background-image: url("data:image/gif;base64,R0lGODlhAwADAIAAAP///8zMzCH5BAAAAAAALAAAAAADAAMAAAIEBHIJBQA7");對于模塊化代碼,合理選擇組合是有必要的。
如果應用程序Web服務器離用戶更近,則一個HTTP請求的相應時間將會被縮短。 另一方面,如果組件web服務器離用戶更近,則多個HTTP請求的相應時間將會被縮短。
內容分發網絡(CDN)是一組分布在多個不同地理位置的web服務器,用于更加有效地向用戶發布內容。通常只在討論性能問題時會提到它的性能,但它還節省成本。
除了縮短響應時間以外,CDN還可以帶來其他優勢。他們的服務包括備份、擴展存儲能力和進行緩存。CDN還有助于緩和web流量峰值的壓力,如在獲取天氣或股市新聞、瀏覽流行的體育或娛樂事件時。
依賴CDN的一個缺點是你的響應時間可能會受到其他網站—-設置可能是你的競爭對手的影響。CDN服務提供商在其所有客戶之間共享其web服務器組。
無法直接控制組件服務器。比如修改HTTP響應頭必須通過服務提供商來完成,而不是由你的工作團隊完成。
如果CDN性能下降,你的工作質量也會隨之下降(不過你可以使用多個CDN服務提供商)。
今天的web頁面都包含了大量的組件,并且數量在不斷增長。頁面的初訪者會進行很多HTTP請求,但通過使用一個長久的Expires頭,使這些組件可以被緩存。這會在后續的頁面瀏覽中減少不必要的HTTP請求。長久的Expires頭最常用與圖片,但應該將其用在所有組件上,包括腳本、樣式表的Flash。
如下響應頭(百度首頁的某張圖片): Response Headers
Accept-Ranges:bytesAge:792098Cache-Control:max-age=2592000Connection:keep-aliveContent-Length:0Content-Type:image/pngDate:Mon, 06 Feb 2017 06:36:17 GMTETag:"5881c862-664"Expires:Mon, 27 Feb 2017 02:34:38 GMTLast-Modified:Fri, 20 Jan 2017 08:20:50 GMTOhc-Response-Time:1 0 0 0 0 0Server:bfe/1.0.8.13-sslpool-patch指定了Expires,即過期時間。但是同時又指定了Cache-Control,這時情況需要另算了。
HTTP 1.1引入了Cache-Control頭來客服Expires頭的限制。因為Expires頭使用一個特定的時間,它要求服務器和客戶端的時鐘嚴格同步。另外,過期時間需要經常檢查,并且一旦這一天到來了,還需要在服務器配置中提供一個新的日期。
換一種方式,Cache-Control使用max-age指定組件被緩存多久(單位是秒)。如果從組件被請求開始過去的秒數少于max-age,瀏覽器就是用緩存的版本,這就避免了額外的HTTP請求。如上述的max-age頭將刷新窗設置為30天。
使用帶有max-age的Cache-Control可以消除Expires的限制,但對于不支持HTTP 1.1的瀏覽器(還有嗎?),如果仍然希望提供Expires頭,可以同時提供兩個響應頭–Expires和Cache-Control。如果兩者同時出現,HTTP規范規定max-age指令將重寫Expires頭。然而,如果你很盡職盡責,你仍然需要擔心Expires帶來的時鐘同步和配置維護問題。
幸運的是,mod_expires Apache模塊使你在使用Expires頭時能夠像max-age那樣以相對的方式設置日期。
關于Cache-Control,不同的操作影響是不同的。見:
http://www.laruence.com/2010/03/05/1332.html
“空緩存”和“完整緩存”指的是與頁面相關的瀏覽器緩存的狀態。如果你的頁面中的組件沒有放在緩存中,則緩存為“空”。瀏覽器的緩存可能包含來自其他網站的組件,但這對你的頁面沒有任何幫助。反之,如果你的頁面中的可緩存組件都在緩存中,則緩存是“完整的”。
Chrome中,如何找到緩存:
chrome://cachechrome://version => 個人資料路徑 => cache/media cache為圖片使用長久的Expires頭非常普遍,但這一最佳實踐不應該僅限于圖片。長久的Expires頭應該包含任何不經常變化的組件,包括腳本、樣式表和Flash組件。但是,HTML文件不應該使用長久的Expires頭,因為它包含動態內容,這些內容需要經常被更新。
如果緩存的文件改變了,那么如何更新呢?當出現了Expires/Cache-Control頭時,直到過期時間為止一直會使用緩存的版本。瀏覽器不會檢查任何更新,直到過了過期日期。這也是為什么使用Expires/Cache-Control頭能夠顯著減少相應時間—-瀏覽器直接從硬盤上讀取組件而無需生成任何HTTP請求。
為了確保用戶能夠獲取組件的最新版本,需要在所有HTML頁面中修改組件的文件名。
最有效的解決方案是修改其所有鏈接,這樣,全新的請求將從原始服務器下載最新的內容。
如果使用php、Perl等動態語言生成HTML頁,一種簡單的解決方案是為所有組件的文件名使用變量。使用這種方法,在頁面中更新文件名時只需要簡單地在某個地方修改變量(如改變版本號xxx_1.1.js)。
一個具有長久的Expires頭的組件將會被緩存,在后續請求中瀏覽器直接從硬盤上讀取它,避免HTTP請求。然而,如果一個組件沒有長久的Expires頭,它仍然會存儲在瀏覽器的緩存中。在后續請求中,瀏覽器會檢查緩存并發現組件已經過期(HTTP術語稱為 陳舊)。為了提高效率,瀏覽器會向原始服務器發送一個條件GET請求。如果組件沒有改變,原始服務器可以免于發送整個組件,而是發送一個很小的頭,告訴瀏覽器可以使用其緩存的組件。
如果設置Cache-Control: no-store
表明該相應因為數據隱私原因不會被緩存,這是相應不會被寫到磁盤上。但是HTTP規范警告說不要依賴這一機制來確保數據的隱私性,惡意的或危險的緩存會完全忽略這個頭。
處理數據隱私性更好的方法是使用安全通信協議如安全套接字層(Secure Layer, SSL)。SSL相應是可緩存的(在ff中只能用于當前瀏覽器回話),因此它提供了一種妥協—-在確保數據隱私性的同時在當前會話中緩存相應以改善用戶體驗。
本規則通過減小HTTP響應的大小來減少相應時間。如果HTTP請求產生的響應包很小,傳輸時間就會減少,因為只需要將很小的包從服務器傳遞到客戶端。
從HTTP 1.1開始,Web客戶端可以通過HTTP請求中的Accept-Encoding頭來標識對壓縮的支持。 Request Headers
GET http://google.com/ HTTP/1.1Host: google.comProxy-Connection: keep-aliveUpgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36X-Client-Data: CKy1yQEIirbJAQiltskBCMG2yQEIqZ3KAQ==Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8Accept-Encoding: gzip, deflate, sdchAccept-Language: zh-CN,zh;q=0.8Cookie: 省略如果web服務器看到請求中有這個頭,就會使用客戶端列出的方法中的一種來壓縮相應。web服務器通過相應中的Content-Encoding頭來通知web客戶端。
Content-Encoding:gzipgzip是目前最流行和最有效的壓縮方法。另一種壓縮是deflate,但效果略遜且不太流行。
通過Apache配置文件、服務器端設置頭部。
當瀏覽器直接與服務器進行通信時,一切都可以正常工作。 當瀏覽器通過代理來發送請求時,情況就復雜了。考慮一下情況:
對于某個url、一個請求來自于支持gzip壓縮的瀏覽器。代理中沒有緩存,會將請求轉發到服務器。此時服務器響應一個壓縮的文件,代理緩存這個文件,并發給服務器。同樣對于這個url,一個請求來自于**不支持**gzip壓縮的瀏覽器,代理仍然將緩存的壓縮文件返回給瀏覽器。這樣就出問題了。解決這一問題的方法是在web服務器的相應中添加Vary頭。web服務器可以告訴代理根據一個或多個請求頭來改變緩存的響應。由于壓縮的決定是基于Accept-Encoding頭的,因此需要在服務器的Vary響應頭中包含Accept-Encoding。
這使得代理會緩存響應的多個版本,為Accept-Encoding請求頭的每個值緩存一份。再根據不同的Accept-Encoding值返回給瀏覽器不同的文件。
如果配置瀏覽器白名單來決定響應是否壓縮(通常在配置文件中),需要:
Vary: Accept-Encoding, User-Agent不同User-Agent值非常多(目前幾大主流瀏覽器還是可以數過來的),這樣設置白名單不是很合理。
關心前端性能的工程師們都希望頁面能逐步地加載,也就是說,我們希望瀏覽器能夠盡快顯示內容。 將樣式表放在文檔底部會導致在瀏覽器中阻止更多內容呈現。為避免樣式變化時重繪頁面中的元素,瀏覽器會堵塞內容逐步呈現。在瀏覽器和用戶等待位于底部的樣式表時,瀏覽器會延遲顯示任何可視化組件,我們稱之為白屏。
在某些瀏覽器中將CSS樣式表放在底部會導致白屏。為了避免白屏,我們應該將樣式表放在文檔頂部的HEAD中(頁面逐步呈現)。將樣式表放在文檔中有兩種方式,使用link標簽和@import規則。
<link rel="stylesheet" href="style1.css"><style> @import url("style2.css")</style>一個style塊可以包含多個@import規則,但@import規則必須放在所有其他規則之前(否則不會加載)。
link除了語法簡單外,還能帶來性能上的收益。@import可能會導致白屏,即使放在HEAD中也是如此。 使用@import規則會導致組件下載的無序性(可能最后下載導致白屏)。
如果樣式表仍在加載,構建呈現樹就是一種浪費,因為所有樣式表加載并解析完畢之前無需繪制任何東西。否則,在其準備好之前顯示內容會遇到FOUC(無樣式內容的閃爍, Flash of Unstyled Content)問題。
白屏是瀏覽器在嘗試修改前端工程師所犯的錯誤—-將樣式表放在文檔比較靠后的位置。白屏是對FOUC問題的彌補。瀏覽器可以延遲呈現,直到所有的樣式表都下載完之后,這就導致了白屏。反之,瀏覽器可以逐步呈現,但要承擔閃爍的風險。這里沒有完美的選擇。所以作為前端工程師,所需要做的就是將樣式表放在HEAD中。
腳本會帶來兩個問題:
腳本會堵塞并行加載,無論放在哪里都是如此對于位于腳本以下的內容,逐步呈現都被堵塞了。將腳本放在頁面越靠下的地方,意味著越多的內容能夠逐步地呈現。瀏覽器會并行地執行HTTP請求。(F12->Network->Timeline - Start Time) HTTP 1.1規范建議瀏覽器從每個主機名并行地下載兩個組件。很多Web頁面需要從一個主機名下載所有組件。
如果一個Web頁面平均地將組件分別放在兩個主機名下,整體響應時間可以減少大約一半(可以并行下載四個組件)。
每個主機并行下載兩個組件只是一個建議。依據瀏覽器不同而不同。
http://www.iefans.net/qingqiu-bingfa-lianjieshu-xianzhi/
前端工程師預期依賴用戶來修改瀏覽器設置,不如簡單地使用DNS將組件放到多個主機名中。但使用并行下載不是沒有開銷的,這取決于你的帶寬的CPU速度,過多的并行下載反而會降低性能。YaHoo!的研究表明,使用兩個主機名比使用1、4/10個主機名能帶來更好的性能(主機數目多會增加DNS解析耗時)。
并行下載的優點是很明顯的。然而,在下載腳本時并行下載實際上是被禁用的(現代瀏覽器一般上是可以的,自己觀察一下)。即使使用了不同的主機名,瀏覽器也不會啟動其他的下載。
其中一個原因是,腳本可能使用document.write來修改頁面內容,因此瀏覽器會等待,以確保頁面能夠恰當地布局。另一個原因是為了保證腳本順序可以按照正確的順序執行。如果并行下載過個腳本,就無法保證相應是按照特定順序到達瀏覽器的。會帶來兩個問題:
堵塞頁面內容的呈現堵塞其他組件的下載CSS表達式是動態設置CSS屬性的強大(但危險)方法。Internet Explorer從第5個版本開始支持CSS表達式.
背景色輪換:background-color: expression((new Date()).getHours() % 2 ? '#000' : '#fff');頁面最小寬度width: expression( document.body.clientwidth < 600 ? '600px' : 'auto'); // IEmin-width: 600px; // other用其他方式代替CSS表達式。
純粹而言,內聯Javascript和CSS會快一些,因為減少了HTTP請求。但是在實際中,還是使用外部文件會產生比較快的頁面。這是因為JavaScript和CSS文件有機會被瀏覽器緩存起來。
每個用戶產生的頁面查看越少,內聯JavaScript和CSS的論據越強勢。反之,如果用戶能夠產生很多的頁面查看,瀏覽器很可能將(具有長久Expires/max-age)外部文件放在緩存中。使用外部文件帶來的收益會隨著每用戶查看頁面次數的增加而增加。
如果網站中每個頁面都使用了相同的JavaScript和CSS,使用外部文件可以提高這些組件的重用率。在這種情況下外部文件更具有優勢,因為當用戶在頁面間導航時,JavaScript和CSS組件已經位于瀏覽器的緩存中了。相反,如果沒有任何兩個頁面共享相同的JavaScript和CSS,重用率就會很低。
在實際情況中,需要在引用外部文件(可以緩存)和內聯文件(減少HTTP請求)之間找到一個平衡點。一般來說,將頁面劃分成幾種不同的頁面類型,然后為每種類型創建單獨的腳本和樣式表。這比維護一個單獨的文件要復雜,但通常比為每個頁面維護不同的腳本和樣式表要容易,并且對于給定的任意頁面都只需下載很少多余的JavaScript和CSS。
對于作為多次頁面查看中的第一次的主頁,我們希望為主頁內聯JavaScript和CSS,但又能為所有后續頁面查看提供外部文件。這可以通過在主頁加載完成后動態下載外部組件來實現(通過load事件)。這能夠將外部文件放到瀏覽器的緩存中以便用戶接下來訪問其他頁面。
<script>window.onload = function() { setTimeout(function(){ var scriptEle = document.createElement('script'); scriptEle.src = 'script1.js'; document.body.appendChild(scriptEle); var linkEle = document.createElement('link'); linkEle.rel = 'stylesheet'; linkEle.type = 'text/css'; linkEle.ref = 'css1.css'; document.head.appendChild(linkEle); }, 1000)}</script>Internet通過IP地址來查找服務器的。DNS將主機名映射到IP地址上。
然而,DNS也是開銷。通常瀏覽器查找一個給定的主機名的IP地址要花費20~120毫秒。在DNS查找完成之前,瀏覽器不能從主機名那里下載任何東西。
查看瀏覽器的DNS緩存(Chrome中在地址欄輸入chrome://net-internals/#dns
)。
在用戶請求了一個主機名之后,DNS信息會留在操作系統的DNS緩存中(windows是“DNS Client”服務),之后對于該主機名的請求將無需進行過多的DNS查找,至少短時間內不需要。
瀏覽器也會擁有自己的緩存,和操作系統的緩存相分離。只要瀏覽器在其緩存中保留了DNS記錄,它就不會麻煩操作系統來請求這個記錄。只有當瀏覽器緩存丟棄了這個記錄時,才會向操作系統詢問地址。
服務器通過設置可以表明記錄被緩存多久。查找返回的DNS記錄包含了一個存活時間(Time-to-live,TTL)值。該值告訴客戶端可以對該記錄緩存多久。
盡管操作系統緩存會考慮TTL值,但瀏覽器通常會忽略該值,并設置它自己的時間限制。此外HTTP協議中的Keep-Alive特性可以同時覆蓋TTL和瀏覽器的時間限制。換句話說,只要瀏覽器的Web服務器愉快地通信著,并保持著TCP連接打開狀態,就沒有理由進行DNS查找。
瀏覽器對緩存的DNS記錄的數量也有限制,而不管緩存記錄的時間。
windows上通過“DNS Client”服務管理DNS緩存。可以使用ipconfig命令來查看和刷新DNS Client服務。
ipconfig /displaydnsipconfig /flushdns重新啟動也可以情況DNS Client緩存。重啟瀏覽器會清空瀏覽器緩存,但不會清空DNS Client服務緩存。
在chrome中通過chrome://net-internals/#dns
可查看dns記錄緩存情況,有意思的是,chrome中的緩存似乎總是長期的,只有數量限制。 。
減少DNS查找意味著使用更少的主機名。使用更少的主機名又意味著減少了并行下載數量。如何權衡? 作者給出的建議是講這些組件放在至少兩個,但不要超過4個主機名下。這是在減少DNS查找和允許高度并行下載之間作出的很好的權衡。
精簡是從代碼中移除不必要的字符以減小其大小,進而改善加載時間的時間。在代碼被精簡后,所有的注釋以及不必要的空白字符都將被移除。這能減小文件體積。
混淆是可以應用在源代碼上的另外一種優化方式。和精簡一樣,它也會移除注釋和空白,同時它還會改寫代碼。作為改寫的一部分,函數和變量的名字將會被轉換為更短的字符串,這時的代碼更加精煉,也更難閱讀。通常這樣做是為了增加對代碼反向工程的難度,但這對提高性能也有幫助,因為這筆精簡更能減少代碼的大小。
這也是可行的。
gzip壓縮可以減少文件體積70%左右。使用精簡后再使用gzip壓縮會帶來更高的效率。
帶來的節省小于精簡JavaScript。
移除注釋和空白合并相同的類,移除不必要的類使用縮寫(用#666代替#666666)移除不必要的字符串(0代替0px)重定向用于將用戶從一個URL重新路由到另一個URL。重定向有很多種,301和302是最常用的兩種。通常對HTML文檔進行重定向,不過也可以用于組件(圖片,腳本等)。重定向的原因有很多,包括網站重新設計、跟蹤流量、記錄廣告點擊和建立易于記憶的URL。但無論如何,重定向會使得頁面變慢。
當web服務器想瀏覽器返回一個重定向時,相應中就會擁有一個范圍在3xx的狀態碼。這表示用戶代理必須執行進一步操作才能完成請求。以下是幾種3xx狀態碼—- - 300 Multiple Choices(基于Content-Type) - 301 Moved Permanently - 302 Moved Temporarily(亦稱Found) - 303 See Other(對302的說明) - 304 Not Modified - 305 Use Proxy - 306(不再使用) - 307 Temporary Redirect(對302的說明)
“304 Not Modified”并不是真正的重定向—-它是用來相應條件GET請求,避免下載已經存在于瀏覽器緩存中的數據。
301和302是用的最多的。303和307是在HTTP 1.1規范中添加的,用來澄清對302的使用(濫用),但是幾乎沒人使用303和307。下面是301響應頭的一個示例:
HTTP 1.1 301 Moved PermanentlyLocation: http://www.baidu.comContent-Type: text/html瀏覽器會自動將用戶帶到Location字段所給出的URL。重定向所需要的信息都出現在這個頭中了。相應提通常是空的。301和302相應在實際中都不會被緩存,除非有附加的頭—-如Expires或Cache-Control。
在meta refresh標簽中可以在其content屬性所指定的秒數后重定向用戶:
<meta http-equiv="refresh" content="5; url=http://www.baidu.com">JavaScript也可以進行重定向,將document.location設置為期望的URL即可。如果你必須要進行重定向,最好的技術是使用標準的3xx HTTP狀態碼。
在重定向完畢并且HTML文檔下載完畢之前,沒有任何東西顯示給用戶。重定向延遲了HTML的加載。
這是最長發生的重定向,也是開發人員最容易忽略的。因為它有充分的理由—-它允許自動索引(自動轉移到默認的index.html)。并且能夠獲得與當前目錄相關的URL(logo.gif)。然而,很多流行的web站點并不依賴自動索引,而是依賴特定的URL和處理器。另外,URL通常也與根目錄相關而不是和當前目錄相關。
注意當主機名后缺少結尾斜線時不會發生重定向。比如,http://www.baidu.com不會發生重定向。然而,通過檢查請求,會發現URL是包含結尾斜線的。導致自動產生斜線的原因是,瀏覽器在進行GET請求時必須制定一些路徑。如果沒有路徑,例如:http://www.baidu.com,它就會簡單地使用文檔根(/):
GET / HTTP 1.1當缺少結尾斜線時發送重定向時很多web服務器的默認行為,包括Apache。
想象一下web后端被重寫的情形,這時新的實現中的URL很可能會有所不同。將用戶從舊的URL轉移到新的URL的最簡單的方式就是重定向。 雖然重定向降低了開發的復雜性,但是這也損害了用戶體驗。整合兩個后端還有其他的選擇,但比起簡單地重定向需要做更多的開發工作,不過這樣非但不會損害用戶體驗,還能使之改善。
Alias、mod_rewrite、DirectorySlash(都是服務器端配置)要求除URL之外還要提交到一個接口(處理器或文件名),但易于實現。如果兩個后端在同一個服務器上,則他們的代碼很可能自己就能連接。例如,舊的處理器代碼通過編程調用新的處理器代碼。如果域名變了,可以使用一個CNAME(一條DNS記錄,用于創建一個域名指向另一個域名的別名)讓兩個主機名指向相同的服務器。如果做到治理點,這里提到的技術(上面兩點)就是可行的。重定向經常用于跟蹤用戶流量的流向。比如一個鏈接被重定向包裝,單擊這個鏈接時,返回一個301相應,Location指向真正的URL。這樣就可以知道用戶的流量去向。
另一種選擇是通過referer日志來跟蹤流量去向。每個HTTP請求都包含一個URL,表明從哪個頁面發起的請求,也就是引用方(有的時候沒有引用頁,如用戶鍵入URL或使用書簽時)。我通過百度搜索打開一個鏈接,在這個鏈接網頁請求頭中發現:
Referer:https://www.baidu.com/link?url=IZv4coYEvm06hEpRFIDlwAB4faRFbtcX7Krs9YZ3HQCu1K0DdD3st5FuIsqXF-TuQSmngS27uZRwyUJUrt5RO_Gu5eDhB62FOl97vLZqjvK&wd=&eqid=dd07dd000002a56200000004589b210e使用Refered日志避免了重定向,也就改善了相應時間。然而,這種方法的難處在于,要相對離開百度的人進行統計,百度就必須分析所有目標網站的日志進行分析。 對于內部流量—-也就是同一家公司的各個網站之間的流量—-值得通過建立Referer日志來避免重定向,以此節省響應時間。如果目標網站屬于其他公司,就不可能分析Referer日志了。
當你嘗試跟蹤用戶流量時,你會發現鏈接可能將用戶帶離你的網站。在這種情況下,使用Referer就不太現實了。
跟蹤出站流量除重定向之外的選擇是使用信標(bacon)—-一個HTTP請求,其URL包含有跟蹤信息。跟蹤信息可以從信標web服務器的訪問日志中提取出來。信標通常是一個1px x 1px的透明圖片;不過204相應更優秀,因為它更小,從不會被緩存,而且絕對不會改變瀏覽器的狀態。
使用重定向可以是URL更加美觀并且易于記憶。改變方式參見上述連接網站。
導致重復腳本有兩個重要因素—-團隊大小和腳本數量。不同的團隊都要處理某個東西(比如cookie),于是他們都添加了腳本(cookie.js)。另外,腳本數量眾多,也會造成混淆。
重復腳本損傷性能的方式有兩種—-不必要的HTTP請求和執行JavaScript所浪費的時間。
實體標簽(Entity Tag,ETag)時web服務器和瀏覽器用于確認緩存有效性的一種機制。在進入ETag的細節之前,我們先回顧一下組件是如何被緩存和確認的。
瀏覽器下載組建時,會將它們保存到緩存中。下次使用這個組件中,如果這個組件時新鮮的,就從硬盤使用這個組件。是否是新鮮的取決于Expires/Cache-Control頭。
如果緩存的組件過期了(或者用戶明確重新加載了頁面),瀏覽器再沖用它之前必須首先檢查它是否仍然有效。這稱作一個一個條件GET請求。不幸的是瀏覽器必須執行這個請求執行有效性檢查,但這仍然比簡單地下載所有已過期的組件的效率要高。如果瀏覽器緩存中的組件是有效的(即它能夠和原始服務器上的組件相匹配),原始服務器不會返回整個組件,而是返回一個“304 Not Modified”狀態碼。
服務器在檢測緩存的組件是否和原始服務器上的組件相匹配時有兩種方式:
比較最新修改日期比較實體標簽瀏覽器通過If-Modified-Since請求頭表明瀏覽器緩存的最新日期。 原始服務器通過Last-Modified響應頭來返回組件的最新修改日期。
如果If-Modified-Since和Last-Modified的值一樣,服務器會返回一個304相應,而不會再次傳送需要的數據。 Request Headers
GET /5aV1bjqh_Q23odCf/static/superman/css/super_min_c53cfdce.css HTTP/1.1Host: ss0.bdstatic.comConnection: keep-aliveCache-Control: max-age=0User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36Accept: text/css,*/*;q=0.1Referer: https://www.baidu.com/Accept-Encoding: gzip, deflate, sdch, brAccept-Language: zh-CN,zh;q=0.8If-None-Match: W/"5881c862-63c6"If-Modified-Since: Fri, 20 Jan 2017 08:20:50 GMTResponse Headers
HTTP/1.1 304 Not ModifiedServer: bfe/1.0.8.13-sslpool-patchDate: Thu, 09 Feb 2017 01:14:24 GMTContent-Type: text/cssConnection: keep-aliveETag: W/"5881c862-63c6"Last-Modified: Fri, 20 Jan 2017 08:20:50 GMTExpires: Sun, 19 Feb 2017 09:04:01 GMTAge: 1699234Cache-Control: max-age=2592000Accept-Ranges: bytesVary: Accept-EncodingContent-Encoding: gzipOhc-Response-Time: 1 0 0 0 0 0可以看出,If-Modified-Since和Last-Modified的值是匹配的。所以使用緩存文件。
ETag提供了另外一種方式,用于檢測緩存中的組件和原始服務器上的組件是否匹配(“實體”是“組件”的另一種稱呼)。ETag在HTTP 1.1引入。ETag是唯一標識了一個組件的特定版本的字符串。唯一的格式約束是該字符串必須用引號引起來。原始服務器使用ETag響應頭來指定組件的ETag。
ETag的加入為驗證實體提供了比最新修改日期更為靈活的機制。例如,如果實體依賴User-Agent或Accept-Language頭而改變,實體的狀態可以反映在ETag中。
此后,如果瀏覽器必須驗證一個組件,它會使用If-None-Match傳回原始服務器。如果ETag是匹配的,就會返回一個304狀態碼。如上。
ETag通常使用組件的某些屬性來構造它,這些屬性對于特定的、寄宿了網站的服務器來說是唯一的。當瀏覽器從一臺服務器上獲取了原始組件后又向另一臺不同的服務器發起條件GET請求時,ETag是不會匹配的—-而對于使用服務器集群來說,這是很常見的一種情況。默認情況下,對于擁有多臺服務器的網站來說Apache和IIS向ETag中嵌入的數據都會大大地降低有效性驗證的成功率。
Apache1.3和2.x的ETag格式是inode-size-timestamp(上述不是)。文件系統使用inode來存儲諸如文件類型、所有者、組和訪問模式等信息。盡管在多臺服務器上一個文件可能位于相同的目錄、具有相同的文件大小、權限、時間戳等,從一臺服務器到另一臺服務器,其inode仍然是不同的。
如果ETag不匹配會讓用戶下載更多內容,用戶體驗會下降。同時,服務器開銷也更大。如果有10臺服務器,則用戶只有10%的機會得到正確的304相應。其余90%的機會會得到20相應并現在所有數據。
ETag還降低了代理緩存的效率。代理后面的用戶緩存的Tag經常和代理緩存的ETag不匹配。這導致不必要的請求被發送到原始服務器。用戶和代理之間不會出現304相應,而是產生兩個又慢又大的200相應—-一個是原始服務器到代理的,一個是從代理到用戶的。ETag的默認格式還可能引入安全性弱點。
更重要的是,If-None-Match比If-Modified-Since具有更高的優先級。你可能希望如果ETag不匹配但最新修改日期是相同的,也能發送一個“304 Not Modified”相應,但實際并不是這樣的。依據HTTP 1.1規范,如果請求中同時出現了這兩個頭,則原始服務器禁止(MUST NOT)返回304(Not Modified),除非請求頭中的條件頭字段全部一致。實際上如果沒有If-None-Match反而會更好一些。
如果你在多臺服務器上寄宿你的網站,而且使用的是具有默認ETag配置的Apache或IIS,用戶將面對緩慢的界面、服務器會具有很高的負載、會消耗大量的帶寬、并且代理也不能有效地緩存內容。即使有長久的Expires/Cache-Control頭,一旦用戶“重新加載”或者“刷新”頁面,還是會產生條件GET請求。ETag還是要被驗證。
一種選擇是對ETag進行配置,以利用其靈活的驗證能力。如:
<?phpif(strops($_SERVER["HTTP_USER_AGENT"], "MSIE")) { header("ETag: MSIE");} else { header("ETag: notMSIE");}?>如果組件必須通過最新修改日期之外的一些東西來進行驗證,則ETag是一種強大的方法。
如果無需自定義ETag,最好簡單地將其移除。Apache和IIS都將ETag視為一個性能問題,并建議修改ETag的內容。可以移除inode值。然而,剩下的(大小,時間戳)是重復信息(Last-Modified和Content-Length頭可以提供完全等價的詳細),所以最好將ETag移除(還可以降低響應頭大小)。
在Apache中,只需向Apache配置文件中簡單地添加一行配置就可以移除ETag:
FileETag noneAjax是異步的,但并代表Ajax就是及時的。這取決于Ajax請求時被動的還是主動的。被動請求是為了將來使用而預先發起的。例如,在一個基于web的email客戶端中,可能會使用被動請求在用戶真正需要之前下載用戶的地址簿。經過被動加載,當用戶需要為一個Email消息添加地址時,客戶端能確保地址簿已經存在于緩存中。主動請求是基于用戶當前的操作而發起的。例如查找所有與用戶搜索條件相匹配的Email消息。
對于主動請求的Ajax,即使是異步的,用戶仍然可能需要等待相應。
其實Ajax緩存就是HTTP緩存。 以下HTTP響應頭是可以用來做Ajax緩存的:
Expires: 應該被應用在你知道內容何時被修改的情況下。 例如,如果是股票價格您可能會設置一個在10秒后過期的數值。對于照片,你可以設置一個更長時間的Expires頭,因為你指望它永遠不改變。Expires頭允許瀏覽器在一段時間內可以重復使用緩存內容,并避免任何不需要的同服務器的交互過程.
Last-Modified: 設置這個標記會通知瀏覽器可以使用If-Modified-Since頭來產生一個條件GET請求以便檢查其本地緩存。如果數據不需要更新,服務器將使用HTTP 304狀態碼來響應此請求
Cache-Control: 如果允許,這應該被設置為’public’,使其他用戶可以在中間代理和緩存服務器上存儲和共享數據,在Firefox上,這還將啟用針對HTTPS的緩存
這里有一篇文章介紹Ajax緩存。
關于這本書的進階指南已經出來了。。有時間有機會再寫吧。要找實習了。 2017.2.9
。
新聞熱點
疑難解答