Web前端新能優化——如何提高頁面載入速度
前言:
在同樣的網路環境下,兩個同樣能滿足你的需求的網站,一個“Duang”的一下就加載出來了,一個糾結了半天才出來,你會選擇哪個?研究表明:使用者最滿意的開啟網頁時間是2-5秒,如果等待超過10秒,99%的使用者會關閉這個網頁。也許這樣講,各位還不會有太多感觸,接下來我列舉一組資料:Google網站訪問速度每慢400ms就導致使用者搜尋請 求下降0.59%;Amazon每增加100ms網站延遲將導致收入下降1%;雅虎如果有400ms延遲會導致流量下降5-9%。網站的載入速度嚴重影響了使用者體驗,也決定了這個網站的生死存亡。
可能有人會說:網站的效能是後端工程師的事情,與前端並無多大關係。我只能說,too young too simple。事實上,只有10%~20%的終端使用者響應時間是用在從Web伺服器獲取HTML文件並傳送到瀏覽器的,那剩餘的時間去哪兒了?來瞄一下效能黃金法則
只有10%~20%的終端使用者響應時間花在了下載HTML文件上。其餘的80%~90%時間花在了下載頁面中的所有元件上。
接下來我們將研究一下前端攻城獅如何來提高頁面的載入速度。
一、減少HTTP請求
上面說到80%~90%時間花在了下載頁面中的所有元件進行的HTTP請求上。因此,改善響應時間最簡單的途徑就是減少HTTP請求的數量。
圖片地圖:
假設導航欄上有五幅圖片,點選每張圖片都會進入一個連結,這樣五張導航的圖片在載入時會產生5個HTTP請求。然而,使用一個圖片地圖可以提高效率,這樣就只需要一個HTTP請求。
伺服器端圖片地圖:將所有點選提交到同一個url,同時提交使用者點選的x、y座標,伺服器端根據座標對映響應
客戶端圖片地圖:直接將點選對映到操作
<img src="planets.jpg" border="0" usemap="#planetmap" alt="Planets" /> <map name="planetmap" id="planetmap"> <area shape="rect" coords="180,139,14" href ="venus.html" alt="Venus" /> <area shape="rect" coords="129,161,10" href ="mercur.html" alt="Mercury"/> <area shape="rect" coords="0,0,110,260" href ="sun.html" alt="Sun" /> <area shape="rect" coords="140,0,110,260" href ="star.html" alt="Sun" /> </map>
使用圖片地圖的缺點:指定座標區域時,矩形或圓形比較容易指定,而其它形狀手工指定比較難
CSS Sprites
CSS Sprites直譯過來就是CSS精靈,但是這種翻譯顯然是不夠的,其實就是通過將多個圖片融合到一副圖裡面,然後通過CSS的一些技術佈局到網頁上。特別是圖片特別多的網站,如果能用css sprites降低圖片數量,帶來的將是速度的提升。
<div> <span id="image1" class="nav"></span> <span id="image2" class="nav"></span> <span id="image3" class="nav"></span> <span id="image4" class="nav"></span> <span id="image5" class="nav"></span> </div>
.nav { width: 50px; height: 50px; display: inline-block; border: 1px solid #000; background-image: url('E:/1.png'); }#image1 { background-position: 0 0; } #image2 { background-position: -95px 0; } #image3 { background-position: -185px 0; } #image4 { background-position: -275px 0; } #image5 { background-position: -366px -3px; }
執行結果:
PS:使用CSS Sprites還有可能降低下載量,可能大家會認為合併後的圖片會比分離圖片的總和要大,因為還有可能會附加空白區域。實際上,合併後的圖片會比分離的圖片總和要小,因為它降低了圖片自身的開銷,譬如顏色表、格式資訊等。
字型圖示
在可以大量使用字型圖示的地方我們可以儘可能使用字型圖示,字型圖示可以減少很多圖片的使用,從而減少http請求,字型圖示還可以通過CSS來設定顏色、大小等樣式,何樂而不為。
合併指令碼 和樣式表
將多個樣式表或者指令碼檔案合併到一個檔案中,可以減少HTTP請求的數量從而縮短效應時間。
然而合併所有檔案對許多人尤其是編寫模組化程式碼的人來說是不能忍的,而且合併所有的樣式檔案或者指令碼檔案可能會導致在一個頁面載入時載入了多於自己所需要的樣式或者指令碼,對於只訪問該網站一個(或幾個)頁面的人來說反而增加了下載量,所以大家應該自己權衡利弊。
二、使用CDN
如果應用程式web伺服器離使用者更近,那麼一個HTTP請求的響應時間將縮短。另一方面,如果元件web伺服器離使用者更近,則多個HTTP請求的響應時間將縮短。
CDN(內容釋出網路)是一組分佈在多個不同地理位置的Web伺服器,用於更加有效地向用戶釋出內容。在優化效能時,向特定使用者釋出內容的伺服器的選擇基於對網路慕課擁堵的測量。例如,CDN可能選擇網路階躍數最小的伺服器,或者具有最短響應時間的伺服器。
CDN還可以進行資料備份、擴充套件儲存能力,進行快取,同時有助於緩和Web流量峰值壓力。
CDN的缺點:
1、響應時間可能會受到其他網站流量的影響。CDN服務提供商在其所有客戶之間共享Web伺服器組。
2、如果CDN服務質量下降了,那麼你的工作質量也將下降
3、無法直接控制組件伺服器
三、新增Expires頭
頁面的初次訪問者會進行很多HTTP請求,但是通過使用一個長久的Expires頭,可以使這些元件被快取,下次訪問的時候,就可以減少不必要的HTPP請求,從而提高載入速度。
Web伺服器通過Expires頭告訴客戶端可以使用一個元件的當前副本,直到指定的時間為止。例如:
Expires: Fri, 18 Mar 2016 07:41:53 GMT
Expires缺點: 它要求伺服器和客戶端時鐘嚴格同步;過期日期需要經常檢查
HTTP1.1中引入Cache-Control來克服Expires頭的限制,使用max-age指定元件被快取多久。
Cache-Control: max-age=12345600
若同時制定Cache-Control和Expires,則max-age將覆蓋Expires頭
四、壓縮元件
從HTTP1.1開始,Web客戶端可以通過HTTP請求中的Accept-Encoding頭來表示對壓縮的支援
Accept-Encoding: gzip,deflate
如果Web伺服器看到請求中有這個頭,就會使用客戶端列出來的方法中的一種來進行壓縮。Web伺服器通過響應中的Content-Encoding來通知 Web客戶端。
Content-Encoding: gzip
代理快取
當瀏覽器通過代理來發送請求時,情況會不一樣。假設針對某個URL傳送到代理的第一個請求來自於一個不支援gzip的瀏覽器。這是代理的第一個請求,快取為空。代理將請求轉發給伺服器。此時響應是未壓縮的,代理快取同時傳送給瀏覽器。現在,假設到達代理的請求是同一個url,來自於一個支援gzip的瀏覽器。代理會使用快取中未壓縮的內容進行響應,從而失去了壓縮的機會。相反,如果第一個瀏覽器支援gzip,第二個不支援,你們代理快取中的壓縮版本將會提供給後續的瀏覽器,而不管它們是否支援gzip。
解決辦法:在web伺服器的響應中新增vary頭Web伺服器可以告訴代理根據一個或多個請求頭來改變快取的響應。因為壓縮的決定是基於Accept-Encoding請求頭的,因此需要在vary響應頭中包含Accept-Encoding。
vary: Accept-Encoding五、將樣式表放在頭部
首先說明一下,將樣式表放在頭部對於實際頁面載入的時間並不能造成太大影響,但是這會減少頁面首屏出現的時間,使頁面內容逐步呈現,改善使用者體驗,防止“白屏”。
我們總是希望頁面能夠儘快顯示內容,為使用者提供視覺化的回饋,這對網速慢的使用者來說是很重要的。
將樣式表放在文件底部會阻止瀏覽器中的內容逐步出現。為了避免當樣式變化時重繪頁面元素,瀏覽器會阻塞內容逐步呈現,造成“白屏”。這源自瀏覽器的行為:如果樣式表仍在載入,構建呈現樹就是一種浪費,因為所有樣式表載入解析完畢之前務虛會之任何東西
六、將指令碼放在底部
更樣式表相同,指令碼放在底部對於實際頁面載入的時間並不能造成太大影響,但是這會減少頁面首屏出現的時間,使頁面內容逐步呈現。
js的下載和執行會阻塞Dom樹的構建(嚴謹地說是中斷了Dom樹的更新),所以script標籤放在首屏範圍內的HTML程式碼段裡會截斷首屏的內容。
下載指令碼時並行下載是被禁用的——即使使用了不同的主機名,也不會啟用其他的下載。因為指令碼可能修改頁面內容,因此瀏覽器會等待;另外,也是為了保證指令碼能夠按照正確的順序執行,因為後面的指令碼可能與前面的指令碼存在依賴關係,不按照順序執行可能會產生錯誤。
七、避免CSS表示式
CSS表示式是動態設定CSS屬性的一種強大並且危險的方式,它受到了IE5以及之後版本、IE8之前版本的支援。
p { width: expression(func(),document.body.clientWidth > 400 ? "400px" : "auto"); height: 80px; border: 1px solid #f00; }
<p><span></span></p> <p><span></span></p> <p><span></span></p> <p><span></span></p> <p><span></span></p> <script> var n = 0; function func() { n++; // alert(); console.log(n); } </script>
滑鼠移動了幾次,函式的執行次數輕而易舉的達到了幾千次,危險性顯而易見。
如何解決:
一次性表示式:
p { width: expression(func(this)); height: 80px; border: 1px solid #f00; }
<p><span></span></p> <p><span></span></p> <p><span></span></p> <p><span></span></p> <p><span></span></p> <script> var n = 0; function func(elem) { n++; elem.style.width = document.body.clientWidth > 400 ? '400px' : "auto"; console.log(n); } </script>
事件處理機制
用js事件處理機制來動態改變元素的樣式,使函式執行次數在可控範圍之內。
八、使用外部的JavaScript和CSS
內聯指令碼或者樣式可以減少HTTP請求,按理來說可以提高頁面載入的速度。然而在實際情況中,當指令碼或者樣式是從外部引入的檔案,瀏覽器就有可能快取它們,從而在以後載入的時候能夠直接使用快取,而HTML文件的大小減小,從而提高載入速度。
影響因素:
1、每個使用者產生的頁面瀏覽量越少,內聯指令碼和樣式的論據越強勢。譬如一個使用者每個月只訪問你的網站一兩次,那麼這種情況下內聯將會更好。而如果該使用者能夠產生很多頁面瀏覽量,那麼快取的樣式和指令碼將會極大減少下載的時間,提交頁面載入速度。
2、如果你的網站不同的頁面之間使用的元件大致相同,那麼使用外部檔案可以提高這些元件的重用率。
載入後下載
有時候我們希望內聯樣式和指令碼,但又可以為接下來的頁面提供外部檔案。那麼我們可以在頁面載入完成止嘔動態載入外部元件,以便使用者接下來的訪問。
1 function doOnload() { 2 setTimeout("downloadFile()",1000); 3 } 4 5 window.onload = doOnload; 6 7 function downloadFile() { 8 downloadCss("http://abc.com/css/a.css"); 9 downloadJS("http://abc.com/js/a.js"); 10 } 11 12 function downloadCss(url) { 13 var ele = document.createElement('link'); 14 ele.rel = "stylesheet"; 15 ele.type = "text/css"; 16 ele.href = url; 17 18 document.body.appendChild(ele); 19 } 20 21 function downloadJS(url) { 22 var ele = document.createElement('script'); 23 ele.src = url; 24 document.body.appendChild(ele); 25 }
在該頁面中,JavaScript和CSS被載入兩次(內聯和外部)。要使其正常工作,必須處理雙重定義。將這些元件放到一個不可見的IFrame中是一個比較好的解決方式。
九、減少DNS查詢
當我們在瀏覽器的位址列輸入網址(譬如: www.linux178.com) ,然後回車,回車這一瞬間到看到頁面到底發生了什麼呢?
域名解析 –> 發起TCP的3次握手 –> 建立TCP連線後發起http請求 –> 伺服器響應http請求,瀏覽器得到html程式碼 –> 瀏覽器解析html程式碼,並請求html程式碼中的資源(如js、css、圖片等) –> 瀏覽器對頁面進行渲染呈現給使用者
域名解析是頁面載入的第一步,那麼域名是如何解析的呢?以Chrome為例:
1. Chrome瀏覽器 會首先搜尋瀏覽器自身的DNS快取(快取時間比較短,大概只有1分鐘,且只能容納1000條快取),看自身的快取中是否有www.linux178.com 對應的條目,而且沒有過期,如果有且沒有過期則解析到此結束。
注:我們怎麼檢視Chrome自身的快取?可以使用 chrome://net-internals/#dns 來進行檢視
2. 如果瀏覽器自身的快取裡面沒有找到對應的條目,那麼Chrome會搜尋作業系統自身的DNS快取,如果找到且沒有過期則停止搜尋解析到此結束.
注:怎麼檢視作業系統自身的DNS快取,以Windows系統為例,可以在命令列下使用 ipconfig /displaydns 來進行檢視
3. 如果在Windows系統的DNS快取也沒有找到,那麼嘗試讀取hosts檔案(位於C:\Windows\System32\drivers\etc),看看這裡面有沒有該域名對應的IP地址,如果有則解析成功。
4. 如果在hosts檔案中也沒有找到對應的條目,瀏覽器就會發起一個DNS的系統呼叫,就會向本地配置的首選DNS伺服器(一般是電信運營商提供的,也可以使用像Google提供的DNS伺服器)發起域名解析請求(通過的是UDP協議向DNS的53埠發起請求,這個請求是遞迴的請求,也就是運營商的DNS伺服器必須得提供給我們該域名的IP地址),運營商的DNS伺服器首先查詢自身的快取,找到對應的條目,且沒有過期,則解析成功。如果沒有找到對應的條目,則有運營商的DNS代我們的瀏覽器發起迭代DNS解析請求,它首先是會找根域的DNS的IP地址(這個DNS伺服器都內建13臺根域的DNS的IP地址),找打根域的DNS地址,就會向其發起請求(請問www.linux178.com這個域名的IP地址是多少啊?),根域發現這是一個頂級域com域的一個域名,於是就告訴運營商的DNS我不知道這個域名的IP地址,但是我知道com域的IP地址,你去找它去,於是運營商的DNS就得到了com域的IP地址,又向com域的IP地址發起了請求(請問www.linux178.com這個域名的IP地址是多少?),com域這臺伺服器告訴運營商的DNS我不知道www.linux178.com這個域名的IP地址,但是我知道linux178.com這個域的DNS地址,你去找它去,於是運營商的DNS又向linux178.com這個域名的DNS地址(這個一般就是由域名註冊商提供的,像萬網,新網等)發起請求(請問www.linux178.com這個域名的IP地址是多少?),這個時候linux178.com域的DNS伺服器一查,誒,果真在我這裡,於是就把找到的結果傳送給運營商的DNS伺服器,這個時候運營商的DNS伺服器就拿到了www.linux178.com這個域名對應的IP地址,並返回給Windows系統核心,核心又把結果返回給瀏覽器,終於瀏覽器拿到了www.linux178.com對應的IP地址,該進行一步的動作了。
注:一般情況下是不會進行以下步驟的
如果經過以上的4個步驟,還沒有解析成功,那麼會進行如下步驟:
5. 作業系統就會查詢NetBIOS name Cache(NetBIOS名稱快取,就存在客戶端電腦中的),那這個快取有什麼東西呢?凡是最近一段時間內和我成功通訊的計算機的計算機名和Ip地址,就都會存在這個快取裡面。什麼情況下該步能解析成功呢?就是該名稱正好是幾分鐘前和我成功通訊過,那麼這一步就可以成功解析。
6. 如果第5步也沒有成功,那會查詢WINS 伺服器(是NETBIOS名稱和IP地址對應的伺服器)
7. 如果第6步也沒有查詢成功,那麼客戶端就要進行廣播查詢
8. 如果第7步也沒有成功,那麼客戶端就讀取LMHOSTS檔案(和HOSTS檔案同一個目錄下,寫法也一樣)
如果第八步還沒有解析成功,那麼就宣告這次解析失敗,那就無法跟目標計算機進行通訊。只要這八步中有一步可以解析成功,那就可以成功和目標計算機進行通訊。
DNS也是開銷,通常瀏覽器查詢一個給定域名的IP地址要花費20~120毫秒,在完成域名解析之前,瀏覽器不能從伺服器載入到任何東西。那麼如何減少域名解析時間,加快頁面載入速度呢?
當客戶端DNS快取(瀏覽器和作業系統)快取為空時,DNS查詢的數量與要載入的Web頁面中唯一主機名的數量相同,包括頁面URL、指令碼、樣式表、圖片、Flash物件等的主機名。減少主機名的 數量就可以減少DNS查詢的數量。
減少唯一主機名的數量會潛在減少頁面中並行下載的數量(HTTP 1.1規範建議從每個主機名並行下載兩個元件,但實際上可以多個),這樣減少主機名和並行下載的方案會產生矛盾,需要大家自己權衡。建議將元件放到至少兩個但不多於4個主機名下,減少DNS查詢的同時也允許高度並行下載。
十、精簡JavaScript
精簡
精簡就是從程式碼中移除不必要的字元以減少檔案大小,降低載入的時間。程式碼精簡的時候會移除不必要的空白字元(空格,換行、製表符),這樣整個檔案的大小就變小了。
混淆
混淆是應用在原始碼上的另外一種方式,它會移除註釋和空白符,同時它還會改寫程式碼。在混淆的時候,函式和變數名將會被轉換成更短的字串,這時程式碼會更加精煉同時難以閱讀。通常這樣做是為了增加對程式碼進行反向工程的難度,這也同時提高了效能。
缺點:
混淆本身比較複雜,可能會引入錯誤。
需要對不能改變的符號做標記,防止JavaScript符號(譬如關鍵字、保留字)被修改。
混淆會使程式碼難以閱讀,這使得在產品環境中除錯問題更加困難。
在以上提到了關於用gzip之類的壓縮方式來壓縮檔案,這邊說明一下,就算使用gzip等方式來壓縮檔案,精簡程式碼依然是有必要的。一般來說,壓縮產生的節省是高於精簡的,在生產環境中,精簡和壓縮同時使用能夠最大限度的獲得更多的節省。
CSS的精簡
CSS的精簡帶來的節省一般來說是小於JavaScript精簡的,因為CSS中註釋和空白相對較少。
除了移除空白、註釋之外,CSS可以通過優化來獲得更多的節省:
合併相同的類;
移除不使用的類;
使用縮寫,譬如
.right { color: #fff; padding-top: 0; margin: 0 10px; border: 1px solid #111 } .wrong { color: #ffffff; padding-top: 0px; margin-top: 0; margin-bottom: 0; margin-left: 10px; margin-right: 10px; border-color: #111; border-width: 1px; border-style: solid; }
上面.right是正確的的寫法,顏色使用縮寫,使用0代替0px,合併可以合併的樣式。另外,在精簡的時候其實樣式最後一行的’;’也是可以省略的。
來看看精簡的例子:
以上分別是jquery-2.0.3的學習版(未精簡)和精簡版,可見精簡檔案的大小比原始檔小了155k,而且,在精簡版中jquery還做了混淆,譬如用e代替window等,從而獲得最大的節省。
十一、避免重定向
什麼是重定向?
重定向用於將使用者從一個URL重新路由到另一個URL。
常用重定向的型別
301:永久重定向,主要用於當網站的域名發生變更之後,告訴搜尋引擎域名已經變更了,應該把舊域名的的資料和連結數轉移到新域名下,從而不會讓網站的排名因域名變更而受到影響。
302:臨時重定向,主要實現post請求後告知瀏覽器轉移到新的URL。
304:Not Modified,主要用於當瀏覽器在其快取中保留了元件的一個副本,同時元件已經過期了,這是瀏覽器就會生成一個條件GET請求,如果伺服器的元件並沒有修改過,則會返回304狀態碼,同時不攜帶主體,告知瀏覽器可以重用這個副本,減少響應大小。
重定向如何損傷效能?
當頁面發生了重定向,就會延遲整個HTML文件的傳輸。在HTML文件到達之前,頁面中不會呈現任何東西,也沒有任何元件會被下載。
來看一個實際例子:對於ASP.NET webform開發來說,對於新手很容易犯一個錯誤,就是把頁面的連線寫成伺服器控制元件後臺程式碼裡,例如用一個Button控制元件,在它的後臺click事件中寫上:Response.Redirect(“”);然而這個Button的作用只是轉移URL,這是非常低效的做法,因為點選Button後,先發送一個Post請求給伺服器,伺服器處理Response.Redirect(“”)後就傳送一個302響應給瀏覽器,瀏覽器再根據響應的URL傳送GET請求。正確的做法應該是在html頁面直接使用a標籤做連結,這樣就避免了多餘的post和重定向。
重定向的應用場景
1. 跟蹤內部流量
重定向經常用於跟蹤使用者流量的方向,當擁有一個門戶主頁的時候,同時想對使用者離開主頁後的流量進行跟蹤,這時可以使用重定向。例如: 某網站主頁新聞的連結地址http://a.com/r/news,點選該連結將產生301響應,其Location被設定為http://news.a.com。通過分析a.com的web伺服器日誌可以得知人們離開首頁之後的去向。
我們知道重定向是如何損傷效能的,為了實現更好的效率,可以使用Referer日誌來跟蹤內部流量去向。每個HTTP請求都有一個Referer表示原始請求頁(除了從書籤開啟或直接鍵入URL等操作),記錄下每個請求的Referer,就避免了向用戶傳送重定向,從而改善了響應時間。
2. 跟蹤出站流量
有時連結可能將使用者帶離你的網站,在這種情況下,使用Referer就不太現實了。
同樣也可以使用重定向來解決跟蹤出站流量問題。以百度搜索為例,百度通過將每個連結包裝到一個302重定向來解決跟蹤的問題,例如搜尋關鍵字“前端效能優化”,搜尋結果中的一個URL為https://www.baidu.com/link?url=pDjwTfa0IAf_FRBNlw1qLDtQ27YBujWp9jPN4q0QSJdNtGtDBK3ja3jyyN2CgxR5aTAywG4SI6V1NypkSyLISWjiFuFQDinhpVn4QE-uLGG&wd=&eqid=9c02bd21001c69170000000556ece297,即使搜尋結果並沒有變,但這個字串是動態改變的,暫時還不知道這裡起到怎樣的作用?(個人感覺:字串中包含了待訪問的網址,點選之後會產生302重定向,將頁面轉到目標頁面(待修改,求大神們給我指正))
除了重定向外,我們還可以選擇使用信標(beacon)——一個HTTP請求,其URL中包含有跟蹤資訊。跟蹤資訊可以從信標Web伺服器的訪問日記中提取出來,信標通常是一個1px*1px的透明圖片,不過204響應更優秀,因為它更小,從來不被快取,而且絕不會改變瀏覽器的狀態。
十二、刪除重複指令碼
在團隊開發一個專案時,由於不同開發者之間都可能會向頁面中新增頁面或元件,因此可能相同的指令碼會被新增多次。
重複的指令碼會造成不必要的HTTP請求(如果沒有快取該指令碼的話),並且執行多餘的JavaScript浪費時間,還有可能造成錯誤。
如何避免重複指令碼呢?
1. 形成良好的指令碼組織。重複指令碼有可能出現在不同的指令碼包含同一段指令碼的情況,有些是必要的,但有些卻不是必要的,所以需要對指令碼進行一個良好的組織。
2. 實現指令碼管理器模組。
例如:
1 function insertScript($file) { 2 if(hadInserted($file)) { 3 return; 4 } 5 exeInsert($file); 6 7 if(hasDependencies($file)) { 8 9 $deps =<span style="color: #000000;"> getDependencies($file); 10 11 foreach ($deps as $script) { 12 insertScript($script); 13 } 14 15 echo "<script type='text/javascript' src='".getVersion($file)."'></script>"; 16 17 } 18 }
先檢查是否插入過,如果插入過則返回。如果該指令碼依賴其它指令碼,則被依賴的指令碼也會被插入。最後指令碼被傳送到頁面,getVersion會檢查指令碼並返回追加了對應版本號的檔名,這樣如果指令碼的版本變化了,那麼以前瀏覽器快取的就會失效。
十三、配置ETag
以前瀏覽器快取的就會失效。
什麼是ETag?
實體標籤(EntityTag)是唯一標識了一個元件的一個特定版本的字串,是web伺服器用於確認快取元件的有效性的一種機制,通常可以使用元件的某些屬性來構造它。
條件GET請求
如果元件過期了,瀏覽器在重用它之前必須首先檢查它是否有效。瀏覽器將傳送一個條件GET請求到伺服器,伺服器判斷快取還有效,則傳送一個304響應,告訴瀏覽器可以重用快取元件。
那麼伺服器是根據什麼判斷快取是否還有效呢?有兩種方式:
ETag(實體標籤);
最新修改日期;
最新修改日期
原始伺服器通過Last-Modified響應頭來返回元件的最新修改日期。
舉個栗子:
當我們不帶快取訪問www.google.com.hk的時候,我們需要下載google的logo,這時會發送這樣一個HTTP請求:
Request:
GET googlelogo_color_272x92dp.png HTTP 1.1
Host: www.google.com.hk
Response:
HTTP 1.1 200 OK
Last-Modified:Fri, 04 Sep 2015 22:33:08 GMT
當需要再次訪問相同元件的時候,同時快取已經過期,瀏覽器會發送如下條件GET請求:
Request:
GET googlelogo_color_272x92dp.png HTTP 1.1
If-Modified-Since:Fri, 04 Sep 2015 22:33:08 GMT
Host: www.google.com.hk
Response:
HTTP 1.1 304 Not Modified
實體標籤
ETag提供了另外一種方式,用於檢測瀏覽器快取中的元件與原始伺服器上的元件是否匹配。摘抄自書上的例子:
不帶快取的請求:
Request:
GET /i/yahoo/gif HTTP 1.1
Host: us.yimg.com
Response:
HTTP 1.1 200 OK
Last-Modified:Tue,12 Dec 200603:03:59 GMT
ETag:”10c24bc-4ab-457elc1f“
再次請求相同元件:
Request:
GET /i/yahoo/gif HTTP 1.1
Host: us.yimg.com
If-Modified-Since:Tue,12 Dec 200603:03:59 GMT
If-None-Match:”10c24bc-4ab-457elc1f“
Response:
HTTP 1.1 304 Not Midified
為什麼要引入ETag?
ETag主要是為了解決Last-Modified無法解決的一些問題:
1. 一些檔案也許會週期性的更改,但是他的內容並不改變(僅僅改變的修改時間),這個時候我們並不希望客戶端認為這個檔案被修改了,而重新GET;
2. 某些檔案修改非常頻繁,比如在秒以下的時間內進行修改,(比方說1s內修改了N次),If-Modified-Since能檢查到的粒度是s級的,這種修改無法判斷(或者說UNIX記錄MTIME只能精確到秒);
3. 某些伺服器不能精確的得到檔案的最後修改時間。
ETag帶來的問題
ETag的問題在於通常使用某些屬性來構造它,有些屬性對於特定的部署了網站的伺服器來說是唯一的。當使用叢集伺服器的時候,瀏覽器從一臺伺服器上獲取了原始元件,之後又向另外一臺不同的伺服器發起條件GET請求,ETag就會出現不匹配的狀況。例如:使用inode-size-timestamp來生成ETag,檔案系統使用inode儲存檔案型別、所有者、組和訪問模式等資訊,在多臺伺服器上,就算檔案大小、許可權、時間戳等都相同,inode也是不同的。
最佳實踐
1. 如果使用Last-Modified不會出現任何問題,可以直接移除ETag,google的搜尋首頁則沒有使用ETag。
2. 確定要使用ETag,在配置ETag的值的時候,移除可能影響到元件叢集伺服器驗證的屬性,例如使用size-timestamp來生成時間戳。
十四、使Ajax可快取
維基百科中這樣定義Ajax:
AJAX即“Asynchronous JavaScript and XML”(非同步的JavaScript與XML技術),指的是一套綜合了多項技術的瀏覽器端網頁開發技術。Ajax的概念由傑西·詹姆士·賈瑞特所提出。
傳統的Web應用允許使用者端填寫表單(form),當提交表單時就向Web伺服器傳送一個請求。伺服器接收並處理傳來的表單,然後送回一個新的網頁,但這個做法浪費了許多頻寬,因為在前後兩個頁面中的大部分HTML碼往往是相同的。由於每次應用的溝通都需要向伺服器傳送請求,應用的迴應時間依賴於伺服器的迴應時間。這導致了使用者介面的迴應比本機應用慢得多。
與此不同,AJAX應用可以僅向伺服器傳送並取回必須的資料,並在客戶端採用JavaScript處理來自伺服器的迴應。因為在伺服器和瀏覽器之間交換的資料大量減少(大約只有原來的5%)[來源請求],伺服器迴應更快了。同時,很多的處理工作可以在發出請求的客戶端機器上完成,因此Web伺服器的負荷也減少了。
類似於DHTML或LAMP,AJAX不是指一種單一的技術,而是有機地利用了一系列相關的技術。雖然其名稱包含XML,但實際上資料格式可以由JSON代替,進一步減少資料量,形成所謂的AJAJ。而客戶端與伺服器也並不需要非同步。一些基於AJAX的“派生/合成”式(derivative/composite)的技術也正在出現,如AFLAX。
Ajax的目地是為突破web本質的開始—停止互動方式,向用戶顯示一個白屏後重繪整個頁面不是一種好的使用者體驗。
非同步與即時
Ajax的一個明顯的有點就是向用戶提供了即時反饋,因為它非同步的從後端web伺服器請求資訊。
使用者是否需要等待的關鍵因素在於Ajax請求是被動的還是主動的。被動請求是為了將來來使用而預先發起的,主動請求是基於使用者當前的操作而發起的
什麼樣的AJAX請求可以被快取?
POST的請求,是不可以在客戶端快取的,每次請求都需要傳送給伺服器進行處理,每次都會返回狀態碼200。(可以在伺服器端對資料進行快取,以便提高處理速度)
GET的請求,是可以(而且預設)在客戶端進行快取的,除非指定了不同的地址,否則同一個地址的AJAX請求,不會重複在伺服器執行,而是返回304。
Ajax請求使用快取
在進行Ajax請求的時候,可以選擇儘量使用get方法,這樣可以使用客戶端的快取,提高請求速度。