前言:
在同樣的網(wǎng)絡(luò)環(huán)境下,兩個同樣能滿足你的需求的網(wǎng)站,一個“Duang”的一下就加載出來了,一個糾結(jié)了半天才出來,你會選擇哪個?研究表明:用戶最滿意的打開網(wǎng)頁時間是2-5秒,如果等待超過10秒,99%的用戶會關(guān)閉這個網(wǎng)頁。也許這樣講,各位還不會有太多感觸,接下來我列舉一組數(shù)據(jù):Google網(wǎng)站訪問速度每慢400ms就導(dǎo)致用戶搜索請 求下降0.59%;Amazon每增加100ms網(wǎng)站延遲將導(dǎo)致收入下降1%;雅虎如果有400ms延遲會導(dǎo)致流量下降5-9%。網(wǎng)站的加載速度嚴(yán)重影響了用戶體驗,也決定了這個網(wǎng)站的生死存亡。
可能有人會說:網(wǎng)站的性能是后端工程師的事情,與前端并無多大關(guān)系。我只能說,too young too simple。事實上,只有10%~20%的最終用戶響應(yīng)時間是用在從Web服務(wù)器獲取HTML文檔并傳送到瀏覽器的,那剩余的時間去哪兒了?來瞄一下性能黃金法則:
只有10%~20%的最終用戶響應(yīng)時間花在了下載HTML文檔上。其余的80%~90%時間花在了下載頁面中的所有組件上。
接下來我們將研究一下前端攻城獅如何來提高頁面的加載速度。
一、減少HTTP請求
上面說到80%~90%時間花在了下載頁面中的所有組件進(jìn)行的HTTP請求上。因此,改善響應(yīng)時間最簡單的途徑就是減少HTTP請求的數(shù)量。
圖片地圖:
假設(shè)導(dǎo)航欄上有五幅圖片,點擊每張圖片都會進(jìn)入一個鏈接,這樣五張導(dǎo)航的圖片在加載時會產(chǎn)生5個HTTP請求。然而,使用一個圖片地圖可以提高效率,這樣就只需要一個HTTP請求。
服務(wù)器端圖片地圖:將所有點擊提交到同一個url,同時提交用戶點擊的x、y坐標(biāo),服務(wù)器端根據(jù)坐標(biāo)映射響應(yīng)
客戶端圖片地圖:直接將點擊映射到操作
<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>
使用圖片地圖的缺點:指定坐標(biāo)區(qū)域時,矩形或圓形比較容易指定,而其它形狀手工指定比較難
CSS Sprites
CSS Sprites直譯過來就是CSS精靈,但是這種翻譯顯然是不夠的,其實就是通過將多個圖片融合到一副圖里面,然后通過CSS的一些技術(shù)布局到網(wǎng)頁上。特別是圖片特別多的網(wǎng)站,如果能用css sprites降低圖片數(shù)量,帶來的將是速度的提升。
<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; }
運行結(jié)果:
PS:使用CSS Sprites還有可能降低下載量,可能大家會認(rèn)為合并后的圖片會比分離圖片的總和要大,因為還有可能會附加空白區(qū)域。實際上,合并后的圖片會比分離的圖片總和要小,因為它降低了圖片自身的開銷,譬如顏色表、格式信息等。
字體圖標(biāo)
在可以大量使用字體圖標(biāo)的地方我們可以盡可能使用字體圖標(biāo),字體圖標(biāo)可以減少很多圖片的使用,從而減少http請求,字體圖標(biāo)還可以通過CSS來設(shè)置顏色、大小等樣式,何樂而不為。
合并腳本 和樣式表
將多個樣式表或者腳本文件合并到一個文件中,可以減少HTTP請求的數(shù)量從而縮短效應(yīng)時間。
然而合并所有文件對許多人尤其是編寫模塊化代碼的人來說是不能忍的,而且合并所有的樣式文件或者腳本文件可能會導(dǎo)致在一個頁面加載時加載了多于自己所需要的樣式或者腳本,對于只訪問該網(wǎng)站一個(或幾個)頁面的人來說反而增加了下載量,所以大家應(yīng)該自己權(quán)衡利弊。
二、使用CDN
如果應(yīng)用程序web服務(wù)器離用戶更近,那么一個HTTP請求的響應(yīng)時間將縮短。另一方面,如果組件web服務(wù)器離用戶更近,則多個HTTP請求的響應(yīng)時間將縮短。
CDN(內(nèi)容發(fā)布網(wǎng)絡(luò))是一組分布在多個不同地理位置的Web服務(wù)器,用于更加有效地向用戶發(fā)布內(nèi)容。在優(yōu)化性能時,向特定用戶發(fā)布內(nèi)容的服務(wù)器的選擇基于對網(wǎng)絡(luò)慕課擁堵的測量。例如,CDN可能選擇網(wǎng)絡(luò)階躍數(shù)最小的服務(wù)器,或者具有最短響應(yīng)時間的服務(wù)器。
CDN還可以進(jìn)行數(shù)據(jù)備份、擴(kuò)展存儲能力,進(jìn)行緩存,同時有助于緩和Web流量峰值壓力。
CDN的缺點:
1、響應(yīng)時間可能會受到其他網(wǎng)站流量的影響。CDN服務(wù)提供商在其所有客戶之間共享Web服務(wù)器組。
2、如果CDN服務(wù)質(zhì)量下降了,那么你的工作質(zhì)量也將下降
3、無法直接控制組件服務(wù)器
三、添加Expires頭
頁面的初次訪問者會進(jìn)行很多HTTP請求,但是通過使用一個長久的Expires頭,可以使這些組件被緩存,下次訪問的時候,就可以減少不必要的HTPP請求,從而提高加載速度。
Web服務(wù)器通過Expires頭告訴客戶端可以使用一個組件的當(dāng)前副本,直到指定的時間為止。例如:
Expires: Fri, 18 Mar 2016 07:41:53 GMT
Expires缺點: 它要求服務(wù)器和客戶端時鐘嚴(yán)格同步;過期日期需要經(jīng)常檢查
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服務(wù)器看到請求中有這個頭,就會使用客戶端列出來的方法中的一種來進(jìn)行壓縮。Web服務(wù)器通過響應(yīng)中的Content-Encoding來通知 Web客戶端。
Content-Encoding: gzip
代理緩存
當(dāng)瀏覽器通過代理來發(fā)送請求時,情況會不一樣。假設(shè)針對某個URL發(fā)送到代理的第一個請求來自于一個不支持gzip的瀏覽器。這是代理的第一個請求,緩存為空。代理將請求轉(zhuǎn)發(fā)給服務(wù)器。此時響應(yīng)是未壓縮的,代理緩存同時發(fā)送給瀏覽器。現(xiàn)在,假設(shè)到達(dá)代理的請求是同一個url,來自于一個支持gzip的瀏覽器。代理會使用緩存中未壓縮的內(nèi)容進(jìn)行響應(yīng),從而失去了壓縮的機(jī)會。相反,如果第一個瀏覽器支持gzip,第二個不支持,你們代理緩存中的壓縮版本將會提供給后續(xù)的瀏覽器,而不管它們是否支持gzip。
解決辦法:在web服務(wù)器的響應(yīng)中添加vary頭Web服務(wù)器可以告訴代理根據(jù)一個或多個請求頭來改變緩存的響應(yīng)。因為壓縮的決定是基于Accept-Encoding請求頭的,因此需要在vary響應(yīng)頭中包含Accept-Encoding。
五、將樣式表放在頭部
首先說明一下,將樣式表放在頭部對于實際頁面加載的時間并不能造成太大影響,但是這會減少頁面首屏出現(xiàn)的時間,使頁面內(nèi)容逐步呈現(xiàn),改善用戶體驗,防止“白屏”。
我們總是希望頁面能夠盡快顯示內(nèi)容,為用戶提供可視化的回饋,這對網(wǎng)速慢的用戶來說是很重要的。
將樣式表放在文檔底部會阻止瀏覽器中的內(nèi)容逐步出現(xiàn)。為了避免當(dāng)樣式變化時重繪頁面元素,瀏覽器會阻塞內(nèi)容逐步呈現(xiàn),造成“白屏”。這源自瀏覽器的行為:如果樣式表仍在加載,構(gòu)建呈現(xiàn)樹就是一種浪費,因為所有樣式表加載解析完畢之前務(wù)虛會之任何東西
六、將腳本放在底部
更樣式表相同,腳本放在底部對于實際頁面加載的時間并不能造成太大影響,但是這會減少頁面首屏出現(xiàn)的時間,使頁面內(nèi)容逐步呈現(xiàn)。
js的下載和執(zhí)行會阻塞Dom樹的構(gòu)建(嚴(yán)謹(jǐn)?shù)卣f是中斷了Dom樹的更新),所以script標(biāo)簽放在首屏范圍內(nèi)的HTML代碼段里會截斷首屏的內(nèi)容。
下載腳本時并行下載是被禁用的——即使使用了不同的主機(jī)名,也不會啟用其他的下載。因為腳本可能修改頁面內(nèi)容,因此瀏覽器會等待;另外,也是為了保證腳本能夠按照正確的順序執(zhí)行,因為后面的腳本可能與前面的腳本存在依賴關(guān)系,不按照順序執(zhí)行可能會產(chǎn)生錯誤。
七、避免CSS表達(dá)式
CSS表達(dá)式是動態(tài)設(shè)置CSS屬性的一種強(qiáng)大并且危險的方式,它受到了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>
鼠標(biāo)移動了幾次,函數(shù)的運行次數(shù)輕而易舉的達(dá)到了幾千次,危險性顯而易見。
如何解決:
一次性表達(dá)式:
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>
事件處理機(jī)制
用js事件處理機(jī)制來動態(tài)改變元素的樣式,使函數(shù)運行次數(shù)在可控范圍之內(nèi)。
八、使用外部的JavaScript和CSS
內(nèi)聯(lián)腳本或者樣式可以減少HTTP請求,按理來說可以提高頁面加載的速度。然而在實際情況中,當(dāng)腳本或者樣式是從外部引入的文件,瀏覽器就有可能緩存它們,從而在以后加載的時候能夠直接使用緩存,而HTML文檔的大小減小,從而提高加載速度。
影響因素:
1、每個用戶產(chǎn)生的頁面瀏覽量越少,內(nèi)聯(lián)腳本和樣式的論據(jù)越強(qiáng)勢。譬如一個用戶每個月只訪問你的網(wǎng)站一兩次,那么這種情況下內(nèi)聯(lián)將會更好。而如果該用戶能夠產(chǎn)生很多頁面瀏覽量,那么緩存的樣式和腳本將會極大減少下載的時間,提交頁面加載速度。
2、如果你的網(wǎng)站不同的頁面之間使用的組件大致相同,那么使用外部文件可以提高這些組件的重用率。
加載后下載
有時候我們希望內(nèi)聯(lián)樣式和腳本,但又可以為接下來的頁面提供外部文件。那么我們可以在頁面加載完成止嘔動態(tài)加載外部組件,以便用戶接下來的訪問。
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被加載兩次(內(nèi)聯(lián)和外部)。要使其正常工作,必須處理雙重定義。將這些組件放到一個不可見的IFrame中是一個比較好的解決方式。
九、減少DNS查找
當(dāng)我們在瀏覽器的地址欄輸入網(wǎng)址(譬如: www.linux178.com) ,然后回車,回車這一瞬間到看到頁面到底發(fā)生了什么呢?
域名解析 --> 發(fā)起TCP的3次握手 --> 建立TCP連接后發(fā)起http請求 --> 服務(wù)器響應(yīng)http請求,瀏覽器得到html代碼 --> 瀏覽器解析html代碼,并請求html代碼中的資源(如js、css、圖片等) --> 瀏覽器對頁面進(jìn)行渲染呈現(xiàn)給用戶
域名解析是頁面加載的第一步,那么域名是如何解析的呢?以Chrome為例:
1. Chrome瀏覽器 會首先搜索瀏覽器自身的DNS緩存(緩存時間比較短,大概只有1分鐘,且只能容納1000條緩存),看自身的緩存中是否有www.linux178.com 對應(yīng)的條目,而且沒有過期,如果有且沒有過期則解析到此結(jié)束。
注:我們怎么查看Chrome自身的緩存?可以使用 chrome://net-internals/#dns 來進(jìn)行查看
2. 如果瀏覽器自身的緩存里面沒有找到對應(yīng)的條目,那么Chrome會搜索操作系統(tǒng)自身的DNS緩存,如果找到且沒有過期則停止搜索解析到此結(jié)束.
注:怎么查看操作系統(tǒng)自身的DNS緩存,以Windows系統(tǒng)為例,可以在命令行下使用 ipconfig /displaydns 來進(jìn)行查看
3. 如果在Windows系統(tǒng)的DNS緩存也沒有找到,那么嘗試讀取hosts文件(位于C:\Windows\System32\drivers\etc),看看這里面有沒有該域名對應(yīng)的IP地址,如果有則解析成功。
4. 如果在hosts文件中也沒有找到對應(yīng)的條目,瀏覽器就會發(fā)起一個DNS的系統(tǒng)調(diào)用,就會向本地配置的首選DNS服務(wù)器(一般是電信運營商提供的,也可以使用像Google提供的DNS服務(wù)器)發(fā)起域名解析請求(通過的是UDP協(xié)議向DNS的53端口發(fā)起請求,這個請求是遞歸的請求,也就是運營商的DNS服務(wù)器必須得提供給我們該域名的IP地址),運營商的DNS服務(wù)器首先查找自身的緩存,找到對應(yīng)的條目,且沒有過期,則解析成功。如果沒有找到對應(yīng)的條目,則有運營商的DNS代我們的瀏覽器發(fā)起迭代DNS解析請求,它首先是會找根域的DNS的IP地址(這個DNS服務(wù)器都內(nèi)置13臺根域的DNS的IP地址),找打根域的DNS地址,就會向其發(fā)起請求(請問www.linux178.com這個域名的IP地址是多少啊?),根域發(fā)現(xiàn)這是一個頂級域com域的一個域名,于是就告訴運營商的DNS我不知道這個域名的IP地址,但是我知道com域的IP地址,你去找它去,于是運營商的DNS就得到了com域的IP地址,又向com域的IP地址發(fā)起了請求(請問www.linux178.com這個域名的IP地址是多少?),com域這臺服務(wù)器告訴運營商的DNS我不知道www.linux178.com這個域名的IP地址,但是我知道linux178.com這個域的DNS地址,你去找它去,于是運營商的DNS又向linux178.com這個域名的DNS地址(這個一般就是由域名注冊商提供的,像萬網(wǎng),新網(wǎng)等)發(fā)起請求(請問www.linux178.com這個域名的IP地址是多少?),這個時候linux178.com域的DNS服務(wù)器一查,誒,果真在我這里,于是就把找到的結(jié)果發(fā)送給運營商的DNS服務(wù)器,這個時候運營商的DNS服務(wù)器就拿到了www.linux178.com這個域名對應(yīng)的IP地址,并返回給Windows系統(tǒng)內(nèi)核,內(nèi)核又把結(jié)果返回給瀏覽器,終于瀏覽器拿到了www.linux178.com對應(yīng)的IP地址,該進(jìn)行一步的動作了。
注:一般情況下是不會進(jìn)行以下步驟的
如果經(jīng)過以上的4個步驟,還沒有解析成功,那么會進(jìn)行如下步驟:
5. 操作系統(tǒng)就會查找NetBIOS name Cache(NetBIOS名稱緩存,就存在客戶端電腦中的),那這個緩存有什么東西呢?凡是最近一段時間內(nèi)和我成功通訊的計算機(jī)的計算機(jī)名和Ip地址,就都會存在這個緩存里面。什么情況下該步能解析成功呢?就是該名稱正好是幾分鐘前和我成功通信過,那么這一步就可以成功解析。
6. 如果第5步也沒有成功,那會查詢WINS 服務(wù)器(是NETBIOS名稱和IP地址對應(yīng)的服務(wù)器)
7. 如果第6步也沒有查詢成功,那么客戶端就要進(jìn)行廣播查找
8. 如果第7步也沒有成功,那么客戶端就讀取LMHOSTS文件(和HOSTS文件同一個目錄下,寫法也一樣)
如果第八步還沒有解析成功,那么就宣告這次解析失敗,那就無法跟目標(biāo)計算機(jī)進(jìn)行通信。只要這八步中有一步可以解析成功,那就可以成功和目標(biāo)計算機(jī)進(jìn)行通信。
DNS也是開銷,通常瀏覽器查找一個給定域名的IP地址要花費20~120毫秒,在完成域名解析之前,瀏覽器不能從服務(wù)器加載到任何東西。那么如何減少域名解析時間,加快頁面加載速度呢?
當(dāng)客戶端DNS緩存(瀏覽器和操作系統(tǒng))緩存為空時,DNS查找的數(shù)量與要加載的Web頁面中唯一主機(jī)名的數(shù)量相同,包括頁面URL、腳本、樣式表、圖片、Flash對象等的主機(jī)名。減少主機(jī)名的 數(shù)量就可以減少DNS查找的數(shù)量。
減少唯一主機(jī)名的數(shù)量會潛在減少頁面中并行下載的數(shù)量(HTTP 1.1規(guī)范建議從每個主機(jī)名并行下載兩個組件,但實際上可以多個),這樣減少主機(jī)名和并行下載的方案會產(chǎn)生矛盾,需要大家自己權(quán)衡。建議將組件放到至少兩個但不多于4個主機(jī)名下,減少DNS查找的同時也允許高度并行下載。
十、精簡JavaScript
精簡
精簡就是從代碼中移除不必要的字符以減少文件大小,降低加載的時間。代碼精簡的時候會移除不必要的空白字符(空格,換行、制表符),這樣整個文件的大小就變小了。
混淆
混淆是應(yīng)用在源代碼上的另外一種方式,它會移除注釋和空白符,同時它還會改寫代碼。在混淆的時候,函數(shù)和變量名將會被轉(zhuǎn)換成更短的字符串,這時代碼會更加精煉同時難以閱讀。通常這樣做是為了增加對代碼進(jìn)行反向工程的難度,這也同時提高了性能。
缺點:
混淆本身比較復(fù)雜,可能會引入錯誤。
需要對不能改變的符號做標(biāo)記,防止JavaScript符號(譬如關(guān)鍵字、保留字)被修改。
混淆會使代碼難以閱讀,這使得在產(chǎn)品環(huán)境中調(diào)試問題更加困難。
在以上提到了關(guān)于用gzip之類的壓縮方式來壓縮文件,這邊說明一下,就算使用gzip等方式來壓縮文件,精簡代碼依然是有必要的。一般來說,壓縮產(chǎn)生的節(jié)省是高于精簡的,在生產(chǎn)環(huán)境中,精簡和壓縮同時使用能夠最大限度的獲得更多的節(jié)省。
CSS的精簡
CSS的精簡帶來的節(jié)省一般來說是小于JavaScript精簡的,因為CSS中注釋和空白相對較少。
除了移除空白、注釋之外,CSS可以通過優(yōu)化來獲得更多的節(jié)省:
合并相同的類;
移除不使用的類;
使用縮寫,譬如
.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的學(xué)習(xí)版(未精簡)和精簡版,可見精簡文件的大小比源文件小了155k,而且,在精簡版中jquery還做了混淆,譬如用e代替window等,從而獲得最大的節(jié)省。
十一、避免重定向
什么是重定向?
重定向用于將用戶從一個URL重新路由到另一個URL。
常用重定向的類型
301:永久重定向,主要用于當(dāng)網(wǎng)站的域名發(fā)生變更之后,告訴搜索引擎域名已經(jīng)變更了,應(yīng)該把舊域名的的數(shù)據(jù)和鏈接數(shù)轉(zhuǎn)移到新域名下,從而不會讓網(wǎng)站的排名因域名變更而受到影響。
302:臨時重定向,主要實現(xiàn)post請求后告知瀏覽器轉(zhuǎn)移到新的URL。
304:Not Modified,主要用于當(dāng)瀏覽器在其緩存中保留了組件的一個副本,同時組件已經(jīng)過期了,這是瀏覽器就會生成一個條件GET請求,如果服務(wù)器的組件并沒有修改過,則會返回304狀態(tài)碼,同時不攜帶主體,告知瀏覽器可以重用這個副本,減少響應(yīng)大小。
重定向如何損傷性能?
當(dāng)頁面發(fā)生了重定向,就會延遲整個HTML文檔的傳輸。在HTML文檔到達(dá)之前,頁面中不會呈現(xiàn)任何東西,也沒有任何組件會被下載。
來看一個實際例子:對于ASP.NET webform開發(fā)來說,對于新手很容易犯一個錯誤,就是把頁面的連接寫成服務(wù)器控件后臺代碼里,例如用一個Button控件,在它的后臺click事件中寫上:Response.Redirect("");然而這個Button的作用只是轉(zhuǎn)移URL,這是非常低效的做法,因為點擊Button后,先發(fā)送一個Post請求給服務(wù)器,服務(wù)器處理Response.Redirect("")后就發(fā)送一個302響應(yīng)給瀏覽器,瀏覽器再根據(jù)響應(yīng)的URL發(fā)送GET請求。正確的做法應(yīng)該是在html頁面直接使用a標(biāo)簽做鏈接,這樣就避免了多余的post和重定向。
重定向的應(yīng)用場景
1. 跟蹤內(nèi)部流量
重定向經(jīng)常用于跟蹤用戶流量的方向,當(dāng)擁有一個門戶主頁的時候,同時想對用戶離開主頁后的流量進(jìn)行跟蹤,這時可以使用重定向。例如: 某網(wǎng)站主頁新聞的鏈接地址http://a.com/r/news,點擊該鏈接將產(chǎn)生301響應(yīng),其Location被設(shè)置為http://news.a.com。通過分析a.com的web服務(wù)器日志可以得知人們離開首頁之后的去向。
我們知道重定向是如何損傷性能的,為了實現(xiàn)更好的效率,可以使用Referer日志來跟蹤內(nèi)部流量去向。每個HTTP請求都有一個Referer表示原始請求頁(除了從書簽打開或直接鍵入URL等操作),記錄下每個請求的Referer,就避免了向用戶發(fā)送重定向,從而改善了響應(yīng)時間。
2. 跟蹤出站流量
有時鏈接可能將用戶帶離你的網(wǎng)站,在這種情況下,使用Referer就不太現(xiàn)實了。
同樣也可以使用重定向來解決跟蹤出站流量問題。以百度搜索為例,百度通過將每個鏈接包裝到一個302重定向來解決跟蹤的問題,例如搜索關(guān)鍵字“前端性能優(yōu)化”,搜索結(jié)果中的一個URL為https://www.baidu.com/link?url=pDjwTfa0IAf_FRBNlw1qLDtQ27YBujWp9jPN4q0QSJdNtGtDBK3ja3jyyN2CgxR5aTAywG4SI6V1NypkSyLISWjiFuFQDinhpVn4QE-uLGG&wd=&eqid=9c02bd21001c69170000000556ece297,即使搜索結(jié)果并沒有變,但這個字符串是動態(tài)改變的,暫時還不知道這里起到怎樣的作用?(個人感覺:字符串中包含了待訪問的網(wǎng)址,點擊之后會產(chǎn)生302重定向,將頁面轉(zhuǎn)到目標(biāo)頁面(待修改,求大神們給我指正))
除了重定向外,我們還可以選擇使用信標(biāo)(beacon)——一個HTTP請求,其URL中包含有跟蹤信息。跟蹤信息可以從信標(biāo)Web服務(wù)器的訪問日記中提取出來,信標(biāo)通常是一個1px*1px的透明圖片,不過204響應(yīng)更優(yōu)秀,因為它更小,從來不被緩存,而且絕不會改變?yōu)g覽器的狀態(tài)。
十二、刪除重復(fù)腳本
在團(tuán)隊開發(fā)一個項目時,由于不同開發(fā)者之間都可能會向頁面中添加頁面或組件,因此可能相同的腳本會被添加多次。
重復(fù)的腳本會造成不必要的HTTP請求(如果沒有緩存該腳本的話),并且執(zhí)行多余的JavaScript浪費時間,還有可能造成錯誤。
如何避免重復(fù)腳本呢?
1. 形成良好的腳本組織。重復(fù)腳本有可能出現(xiàn)在不同的腳本包含同一段腳本的情況,有些是必要的,但有些卻不是必要的,所以需要對腳本進(jìn)行一個良好的組織。
2. 實現(xiàn)腳本管理器模塊。
例如:
1 function insertScript($file) { 2 if(hadInserted($file)) { 3 return; 4 } 5 exeInsert($file); 6 7 if(hasDependencies($file)) { 8 9 $deps = 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會檢查腳本并返回追加了對應(yīng)版本號的文件名,這樣如果腳本的版本變化了,那么以前瀏覽器緩存的就會失效。
十三、配置ETag
以前瀏覽器緩存的就會失效。
什么是ETag?
實體標(biāo)簽(EntityTag)是唯一標(biāo)識了一個組件的一個特定版本的字符串,是web服務(wù)器用于確認(rèn)緩存組件的有效性的一種機(jī)制,通常可以使用組件的某些屬性來構(gòu)造它。
條件GET請求
如果組件過期了,瀏覽器在重用它之前必須首先檢查它是否有效。瀏覽器將發(fā)送一個條件GET請求到服務(wù)器,服務(wù)器判斷緩存還有效,則發(fā)送一個304響應(yīng),告訴瀏覽器可以重用緩存組件。
那么服務(wù)器是根據(jù)什么判斷緩存是否還有效呢?有兩種方式:
ETag(實體標(biāo)簽);
最新修改日期;
最新修改日期
原始服務(wù)器通過Last-Modified響應(yīng)頭來返回組件的最新修改日期。
舉個栗子:
當(dāng)我們不帶緩存訪問www.google.com.hk的時候,我們需要下載google的logo,這時會發(fā)送這樣一個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
當(dāng)需要再次訪問相同組件的時候,同時緩存已經(jīng)過期,瀏覽器會發(fā)送如下條件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
實體標(biāo)簽
ETag提供了另外一種方式,用于檢測瀏覽器緩存中的組件與原始服務(wù)器上的組件是否匹配。摘抄自書上的例子:
不帶緩存的請求:
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. 一些文件也許會周期性的更改,但是他的內(nèi)容并不改變(僅僅改變的修改時間),這個時候我們并不希望客戶端認(rèn)為這個文件被修改了,而重新GET;
2. 某些文件修改非常頻繁,比如在秒以下的時間內(nèi)進(jìn)行修改,(比方說1s內(nèi)修改了N次),If-Modified-Since能檢查到的粒度是s級的,這種修改無法判斷(或者說UNIX記錄MTIME只能精確到秒);
3. 某些服務(wù)器不能精確的得到文件的最后修改時間。
ETag帶來的問題
ETag的問題在于通常使用某些屬性來構(gòu)造它,有些屬性對于特定的部署了網(wǎng)站的服務(wù)器來說是唯一的。當(dāng)使用集群服務(wù)器的時候,瀏覽器從一臺服務(wù)器上獲取了原始組件,之后又向另外一臺不同的服務(wù)器發(fā)起條件GET請求,ETag就會出現(xiàn)不匹配的狀況。例如:使用inode-size-timestamp來生成ETag,文件系統(tǒng)使用inode存儲文件類型、所有者、組和訪問模式等信息,在多臺服務(wù)器上,就算文件大小、權(quán)限、時間戳等都相同,inode也是不同的。
最佳實踐
1. 如果使用Last-Modified不會出現(xiàn)任何問題,可以直接移除ETag,google的搜索首頁則沒有使用ETag。
2. 確定要使用ETag,在配置ETag的值的時候,移除可能影響到組件集群服務(wù)器驗證的屬性,例如使用size-timestamp來生成時間戳。
十四、使Ajax可緩存
維基百科中這樣定義Ajax:
AJAX即“Asynchronous JavaScript and XML”(異步的JavaScript與XML技術(shù)),指的是一套綜合了多項技術(shù)的瀏覽器端網(wǎng)頁開發(fā)技術(shù)。Ajax的概念由杰西·詹姆士·賈瑞特所提出。
傳統(tǒng)的Web應(yīng)用允許用戶端填寫表單(form),當(dāng)提交表單時就向Web服務(wù)器發(fā)送一個請求。服務(wù)器接收并處理傳來的表單,然后送回一個新的網(wǎng)頁,但這個做法浪費了許多帶寬,因為在前后兩個頁面中的大部分HTML碼往往是相同的。由于每次應(yīng)用的溝通都需要向服務(wù)器發(fā)送請求,應(yīng)用的回應(yīng)時間依賴于服務(wù)器的回應(yīng)時間。這導(dǎo)致了用戶界面的回應(yīng)比本機(jī)應(yīng)用慢得多。
與此不同,AJAX應(yīng)用可以僅向服務(wù)器發(fā)送并取回必須的數(shù)據(jù),并在客戶端采用JavaScript處理來自服務(wù)器的回應(yīng)。因為在服務(wù)器和瀏覽器之間交換的數(shù)據(jù)大量減少(大約只有原來的5%)[來源請求],服務(wù)器回應(yīng)更快了。同時,很多的處理工作可以在發(fā)出請求的客戶端機(jī)器上完成,因此Web服務(wù)器的負(fù)荷也減少了。
類似于DHTML或LAMP,AJAX不是指一種單一的技術(shù),而是有機(jī)地利用了一系列相關(guān)的技術(shù)。雖然其名稱包含XML,但實際上數(shù)據(jù)格式可以由JSON代替,進(jìn)一步減少數(shù)據(jù)量,形成所謂的AJAJ。而客戶端與服務(wù)器也并不需要異步。一些基于AJAX的“派生/合成”式(derivative/composite)的技術(shù)也正在出現(xiàn),如AFLAX。
Ajax的目地是為突破web本質(zhì)的開始—停止交互方式,向用戶顯示一個白屏后重繪整個頁面不是一種好的用戶體驗。
異步與即時
Ajax的一個明顯的有點就是向用戶提供了即時反饋,因為它異步的從后端web服務(wù)器請求信息。
用戶是否需要等待的關(guān)鍵因素在于Ajax請求是被動的還是主動的。被動請求是為了將來來使用而預(yù)先發(fā)起的,主動請求是基于用戶當(dāng)前的操作而發(fā)起的
什么樣的AJAX請求可以被緩存?
POST的請求,是不可以在客戶端緩存的,每次請求都需要發(fā)送給服務(wù)器進(jìn)行處理,每次都會返回狀態(tài)碼200。(可以在服務(wù)器端對數(shù)據(jù)進(jìn)行緩存,以便提高處理速度)
GET的請求,是可以(而且默認(rèn))在客戶端進(jìn)行緩存的,除非指定了不同的地址,否則同一個地址的AJAX請求,不會重復(fù)在服務(wù)器執(zhí)行,而是返回304。
Ajax請求使用緩存
在進(jìn)行Ajax請求的時候,可以選擇盡量使用get方法,這樣可以使用客戶端的緩存,提高請求速度。