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