摘要:避免重定向重定向用和狀態(tài)碼,下面是一個(gè)有狀態(tài)碼的頭瀏覽器會(huì)自動(dòng)跳轉(zhuǎn)到域指明的。除此之外還有別的跳轉(zhuǎn)方式元標(biāo)簽和,但如果你必須得做重定向,最好用標(biāo)準(zhǔn)的狀態(tài)碼,主要是為了讓返回按鈕能正常使用。要提高性能,優(yōu)化這些響應(yīng)至關(guān)重要。
一直以來,性能優(yōu)化是開發(fā)的重中之中,而提及 前端性能優(yōu)化 ,大家應(yīng)該都會(huì)想到 雅虎軍規(guī),本文會(huì)結(jié)合 “雅虎軍規(guī)” 融入自己的了解知識(shí),進(jìn)行的總結(jié)和梳理。希望對(duì)大家無論是開發(fā)中還是面試中都能有所幫助!
80%的終端用戶響應(yīng)時(shí)間都花在了前端上,其中大部分時(shí)間都在下載頁面上的各種組件:圖片,樣式表,腳本,F(xiàn)lash等等。減少組件數(shù)必然能夠減少頁面提交的HTTP請(qǐng)求數(shù)。這是讓頁面更快的關(guān)鍵。
減少頁面組件數(shù)的一種方式是簡(jiǎn)化頁面設(shè)計(jì)。但有沒有一種方法可以在構(gòu)建復(fù)雜的頁面同時(shí)加快響應(yīng)時(shí)間呢?嗯,確實(shí)有魚和熊掌兼得的辦法。
合并文件是通過把所有腳本放在一個(gè)文件中的方式來減少請(qǐng)求數(shù)的,當(dāng)然,也可以合并所有的CSS。如果各個(gè)頁面的腳本和樣式不一樣的話,合并文件就是一項(xiàng)比較麻煩的工作了,但把這個(gè)作為站點(diǎn)發(fā)布過程的一部分確實(shí)可以提高響應(yīng)時(shí)間。
CSS Sprites是減少圖片請(qǐng)求數(shù)量的首選方式。把背景圖片都整合到一張圖片中,然后用CSS的background-image和background-position屬性來定位要顯示的部分。
圖像映射可以把多張圖片合并成單張圖片,總大小是一樣的,但減少了請(qǐng)求數(shù)并加速了頁面加載。圖片映射只有在圖像在頁面中連續(xù)的時(shí)候才有用,比如導(dǎo)航條。給image map設(shè)置坐標(biāo)的過程既無聊又容易出錯(cuò),用image map來做導(dǎo)航也不容易,所以不推薦用這種方式。
行內(nèi)圖片(Base64編碼)用data: URL模式來把圖片嵌入頁面。這樣會(huì)增加HTML文件的大小,把行內(nèi)圖片放在(緩存的)樣式表中是個(gè)好辦法,而且成功避免了頁面變“重”。但目前主流瀏覽器并不能很好地支持行內(nèi)圖片。
減少頁面的HTTP請(qǐng)求數(shù)是個(gè)起點(diǎn),這是提升站點(diǎn)首次訪問速度的重要指導(dǎo)原則。
2.減少DNS查找域名系統(tǒng)建立了主機(jī)名和IP地址間的映射,就像電話簿上人名和號(hào)碼的映射一樣。當(dāng)你在瀏覽器輸入www.yahoo.com的時(shí)候,瀏覽器就會(huì)聯(lián)系DNS解析器返回服務(wù)器的IP地址。DNS是有成本的,它需要20到120毫秒去查找給定主機(jī)名的IP地址。在DNS查找完成之前,瀏覽器無法從主機(jī)名下載任何東西。
DNS查找被緩存起來更高效,由用戶的ISP(網(wǎng)絡(luò)服務(wù)提供商)或者本地網(wǎng)絡(luò)存在一個(gè)特殊的緩存服務(wù)器上,但還可以緩存在個(gè)人用戶的計(jì)算機(jī)上。DNS信息被保存在操作系統(tǒng)的DNS cache(微軟Windows上的”DNS客戶端服務(wù)”)里。大多數(shù)瀏覽器有獨(dú)立于操作系統(tǒng)的自己的cache。只要瀏覽器在自己的cache里還保留著這條記錄,它就不會(huì)向操作系統(tǒng)查詢DNS。
IE默認(rèn)緩存DNS查找30分鐘,寫在DnsCacheTimeout注冊(cè)表設(shè)置中。Firefox緩存1分鐘,可以用network.dnsCacheExpiration配置項(xiàng)設(shè)置。(Fasterfox把緩存時(shí)間改成了1小時(shí) P.S. Fasterfox是FF的一個(gè)提速插件)
如果客戶端的DNS cache是空的(包括瀏覽器的和操作系統(tǒng)的),DNS查找數(shù)等于頁面上不同的主機(jī)名數(shù),包括頁面URL,圖片,腳本文件,樣式表,F(xiàn)lash對(duì)象等等組件中的主機(jī)名,減少不同的主機(jī)名就可以減少DNS查找。
減少不同主機(jī)名的數(shù)量同時(shí)也減少了頁面能夠并行下載的組件數(shù)量,避免DNS查找削減了響應(yīng)時(shí)間,而減少并行下載數(shù)量卻增加了響應(yīng)時(shí)間。我的原則是把組件分散在2到4個(gè)主機(jī)名下,這是同時(shí)減少DNS查找和允許高并發(fā)下載的折中方案。
3.避免重定向重定向用301和302狀態(tài)碼,下面是一個(gè)有301狀態(tài)碼的HTTP頭:
HTTP/1.1 301 Moved Permanently Location: http://example.com/newuri Content-Type: text/html
瀏覽器會(huì)自動(dòng)跳轉(zhuǎn)到Location域指明的URL。重定向需要的所有信息都在HTTP頭部,而響應(yīng)體一般是空的。其實(shí)額外的HTTP頭,比如Expires和Cache-Control也表示重定向。除此之外還有別的跳轉(zhuǎn)方式:refresh元標(biāo)簽和JavaScript,但如果你必須得做重定向,最好用標(biāo)準(zhǔn)的3xxHTTP狀態(tài)碼,主要是為了讓返回按鈕能正常使用。
牢記重定向會(huì)拖慢用戶體驗(yàn),在用戶和HTML文檔之間插入重定向會(huì)延遲頁面上的所有東西,頁面無法渲染,組件也無法開始下載,直到HTML文檔被送達(dá)瀏覽器。
有一種常見的極其浪費(fèi)資源的重定向,而且web開發(fā)人員一般都意識(shí)不到這一點(diǎn),就是URL尾部缺少一個(gè)斜線的時(shí)候。例如,跳轉(zhuǎn)到http://astrology.yahoo.com/as...://astrology.yahoo.com/astrology/的301響應(yīng)(注意添在尾部的斜線)。在Apache中可以用Alias,mod_rewrite或者DirectorySlash指令來取消不必要的重定向。
重定向最常見的用途是把舊站點(diǎn)連接到新的站點(diǎn),還可以連接同一站點(diǎn)的不同部分,針對(duì)用戶的不同情況(瀏覽器類型,用戶帳號(hào)類型等等)做一些處理。用重定向來連接兩個(gè)網(wǎng)站是最簡(jiǎn)單的,只需要少量的額外代碼。雖然在這些時(shí)候使用重定向減少了開發(fā)人員的開發(fā)復(fù)雜度,但降低了用戶體驗(yàn)。一種替代方案是用Alias和mod_rewrite,前提是兩個(gè)代碼路徑都在相同的服務(wù)器上。如果是因?yàn)橛蛎兓褂昧酥囟ㄏ颍涂梢詣?chuàng)建一條CNAME(創(chuàng)建一個(gè)指向另一個(gè)域名的DNS記錄作為別名)結(jié)合Alias或者mod_rewrite指令。
4.讓Ajax可緩存Ajax的一個(gè)好處是可以給用戶提供即時(shí)反饋,因?yàn)樗軌驈暮笈_(tái)服務(wù)器異步請(qǐng)求信息。然而,用了Ajax就無法保證用戶在等待異步JavaScript和XML響應(yīng)返回期間不會(huì)非常無聊。在很多應(yīng)用程序中,用戶能夠一直等待取決于如何使用Ajax。例如,在基于web的電子郵件客戶端中,用戶為了尋找符合他們搜索標(biāo)準(zhǔn)的郵件消息,將會(huì)保持對(duì)Ajax請(qǐng)求返回結(jié)果的關(guān)注。重要的是,要記得“異步”并不意味著“即時(shí)”。
要提高性能,優(yōu)化這些Ajax響應(yīng)至關(guān)重要。最重要的提高Ajax性能的方法就是讓響應(yīng)變得可緩存,就像在添上Expires或者Cache-Control HTTP頭中討論的一樣。下面適用于Ajax的其它規(guī)則:
Gzip組件
減少DNS查找
壓縮JavaScript
避免重定向
配置ETags
我們一起看看例子,一個(gè)Web 2.0的電子郵件客戶端用了Ajax來下載用戶的通訊錄,以便實(shí)現(xiàn)自動(dòng)完成功能。如果用戶從上一次使用之后再?zèng)]有修改過她的通訊錄,而且Ajax響應(yīng)是可緩存的,有尚未過期的Expires或者Cache-Control HTTP頭,那么之前的通訊錄就可以從緩存中讀出。必須通知瀏覽器,應(yīng)該繼續(xù)使用之前緩存的通訊錄響應(yīng),還是去請(qǐng)求一個(gè)新的??梢酝ㄟ^給通訊錄的Ajax URL里添加一個(gè)表明用戶通訊錄最后修改時(shí)間的時(shí)間戳來實(shí)現(xiàn),例如&t=1190241612。如果通訊錄從上一次下載之后再?zèng)]有被修改過,時(shí)間戳不變,通訊錄就將從瀏覽器緩存中直接讀出,從而避免一次額外的HTTP往返消耗。如果用戶已經(jīng)修改了通訊錄,時(shí)間戳也可以確保新的URL不會(huì)匹配緩存的響應(yīng),瀏覽器將請(qǐng)求新的通訊錄條目。
即使Ajax響應(yīng)是動(dòng)態(tài)創(chuàng)建的,而且可能只適用于單用戶,它們也可以被緩存,而這樣會(huì)讓你的Web 2.0應(yīng)用更快。
5.延遲加載組件可以湊近看看頁面并問自己:什么才是一開始渲染頁面所必須的?其余內(nèi)容都可以等會(huì)兒。
JavaScript是分隔onload事件之前和之后的一個(gè)理想選擇。例如,如果有JavaScript代碼和支持拖放以及動(dòng)畫的庫,這些都可以先等會(huì)兒,因?yàn)橥戏旁厥窃陧撁孀畛蹁秩局蟮?。其它可以延遲加載的部分包括隱藏內(nèi)容(在某個(gè)交互動(dòng)作之后才出現(xiàn)的內(nèi)容)和折疊的圖片。
工具可幫你減輕工作量:YUI Image Loader可以延遲加載折疊的圖片,還有YUI Get utility是一種引入JS和CSS的簡(jiǎn)單方法。Yahoo!主頁就是一個(gè)例子,可以打開Firebug的網(wǎng)絡(luò)面板仔細(xì)看看。
最好讓性能目標(biāo)符合其它web開發(fā)最佳實(shí)踐,比如“漸進(jìn)增強(qiáng)”。如果客戶端支持JavaScript,可以提高用戶體驗(yàn),但必須確保頁面在不支持JavaScript時(shí)也能正常工作。所以,在確定頁面運(yùn)行正常之后,可以用一些延遲加載腳本增強(qiáng)它,以支持一些拖放和動(dòng)畫之類的華麗效果。
6.預(yù)加載組件預(yù)加載可能看起來和延遲加載是相反的,但它其實(shí)有不同的目標(biāo)。通過預(yù)加載組件可以充分利用瀏覽器空閑的時(shí)間來請(qǐng)求將來會(huì)用到的組件(圖片,樣式和腳本)。用戶訪問下一頁的時(shí)候,大部分組件都已經(jīng)在緩存里了,所以在用戶看來頁面會(huì)加載得更快。
實(shí)際應(yīng)用中有以下幾種預(yù)加載的類型:
無條件預(yù)加載——盡快開始加載,獲取一些額外的組件。google.com就是一個(gè)sprite圖片預(yù)加載的好例子,這個(gè)sprite圖片并不是google.com主頁需要的,而是搜索結(jié)果頁面上的內(nèi)容。
條件性預(yù)加載——根據(jù)用戶操作猜測(cè)用戶將要跳轉(zhuǎn)到哪里并據(jù)此預(yù)加載。在search.yahoo.com的輸入框里鍵入內(nèi)容后,可以看到那些額外組件是怎樣請(qǐng)求加載的。
提前預(yù)加載——在推出新設(shè)計(jì)之前預(yù)加載。經(jīng)常在重新設(shè)計(jì)之后會(huì)聽到:“這個(gè)新網(wǎng)站不錯(cuò),但比以前更慢了”,一部分原因是用戶訪問先前的頁面都是有舊緩存的,但新的卻是一種空緩存狀態(tài)下的體驗(yàn)??梢酝ㄟ^在將要推出新設(shè)計(jì)之前預(yù)加載一些組件來減輕這種負(fù)面影響,老站可以利用瀏覽器空閑的時(shí)間來請(qǐng)求那些新站需要的圖片和腳本。
一個(gè)復(fù)雜的頁面意味著要下載更多的字節(jié),而且用JavaScript訪問DOM也會(huì)更慢。舉個(gè)例子,想要添加一個(gè)事件處理器的時(shí)候,循環(huán)遍歷頁面上的500個(gè)DOM元素和5000個(gè)DOM元素是有區(qū)別的。
大量的DOM元素是一種征兆——頁面上有些內(nèi)容無關(guān)的標(biāo)記需要清理。正在用嵌套表格來布局嗎?還是為了修復(fù)布局問題而添了一堆的
YUI CSS utilities對(duì)布局有很大幫助:grids.css針對(duì)整體布局,fonts.css和reset.css可以用來去除瀏覽器的默認(rèn)格式。這是個(gè)開始清理和思考標(biāo)記的好機(jī)會(huì),例如只在語義上有意義的時(shí)候使用
DOM元素的數(shù)量很容易測(cè)試,只需要在Firebug的控制臺(tái)里輸入:
document.getElementsByTagName("*").length
那么多少DOM元素才算是太多呢?可以參考其它類似的標(biāo)記良好的頁面,例如Yahoo!主頁是一個(gè)相當(dāng)繁忙的頁面,但只有不到700個(gè)元素(HTML標(biāo)簽)。
8.跨域分離組件分離組件可以最大化并行下載,但要確保只用不超過2-4個(gè)域,因?yàn)榇嬖贒NS查找的代價(jià)。例如,可以把HTML和動(dòng)態(tài)內(nèi)容部署在www.example.org,而把靜態(tài)組件分離到static1.example.org和static2.example.org。
9.盡量少用iframe用iframe可以把一個(gè)HTML文檔插入到父文檔里,重要的是明白iframe是如何工作的并高效地使用它。
引入緩慢的第三方內(nèi)容,比如標(biāo)志和廣告
安全沙箱
并行下載腳本
代價(jià)高昂,即使是空白的iframe
阻塞頁面加載
非語義
10.杜絕404HTTP請(qǐng)求代價(jià)高昂,完全沒有必要用一個(gè)HTTP請(qǐng)求去獲取一個(gè)無用的響應(yīng)(比如404 Not Found),只會(huì)拖慢用戶體驗(yàn)而沒有任何好處。
有些站點(diǎn)用的是有幫助的404——“你的意思是xxx?”,這樣做有利于用戶體驗(yàn),,但也浪費(fèi)了服務(wù)器資源(比如數(shù)據(jù)庫等等)。最糟糕的是鏈接到的外部JavaScript有錯(cuò)誤而且結(jié)果是404。首先,這種下載將阻塞并行下載。其次,瀏覽器會(huì)試圖解析404響應(yīng)體,因?yàn)樗荍avaScript代碼,需要找出其中可用的部分。
用CSS表達(dá)式動(dòng)態(tài)設(shè)置CSS屬性,是一種強(qiáng)大又危險(xiǎn)的方式。從IE5開始支持,但從IE8起就不推薦使用了。例如,可以用CSS表達(dá)式把背景顏色設(shè)置成按小時(shí)交替的:
background-color: expression( (new Date()).getHours()%2 ? "#B8D4FF" : "#F08A00" );12.選擇舍棄@import
前面提到了一個(gè)最佳實(shí)踐:為了實(shí)現(xiàn)逐步渲染,CSS應(yīng)該放在頂部。
在IE中用@import與在底部用效果一樣,所以最好不要用它。
13.避免使用濾鏡IE專有的AlphaImageLoader濾鏡可以用來修復(fù)IE7之前的版本中半透明PNG圖片的問題。在圖片加載過程中,這個(gè)濾鏡會(huì)阻塞渲染,卡住瀏覽器,還會(huì)增加內(nèi)存消耗而且是被應(yīng)用到每個(gè)元素的,而不是每個(gè)圖片,所以會(huì)存在一大堆問題。
最好的方法是干脆不要用AlphaImageLoader,而優(yōu)雅地降級(jí)到用在IE中支持性很好的PNG8圖片來代替。如果非要用AlphaImageLoader,應(yīng)該用下劃線hack:_filter來避免影響IE7及更高版本的用戶。
14.把樣式表放在頂部在Yahoo!研究性能的時(shí)候,我們發(fā)現(xiàn)把樣式表放到文檔的HEAD部分能讓頁面看起來加載地更快。這是因?yàn)榘褬邮奖矸旁趆ead里能讓頁面逐步渲染。
關(guān)注性能的前端工程師想讓頁面逐步渲染。也就是說,我們想讓瀏覽器盡快顯示已有內(nèi)容,這在頁面上有一大堆內(nèi)容或者用戶網(wǎng)速很慢時(shí)顯得尤為重要。給用戶顯示反饋(比如進(jìn)度指標(biāo))的重要性已經(jīng)被廣泛研究過,并且被記錄下來了。在我們的例子中,HTML頁面就是進(jìn)度指標(biāo)!當(dāng)瀏覽器逐漸加載頁面頭部,導(dǎo)航條,頂部logo等等內(nèi)容的時(shí)候,這些都被正在等待頁面加載的用戶當(dāng)作反饋,能夠提高整體用戶體驗(yàn)。
js部分 15.去除重復(fù)腳本頁面含有重復(fù)的腳本文件會(huì)影響性能,這可能和你想象的不一樣。在對(duì)美國前10大web站點(diǎn)的評(píng)審中,發(fā)現(xiàn)只有2個(gè)站點(diǎn)含有重復(fù)腳本。兩個(gè)主要原因增加了在單一頁面中出現(xiàn)重復(fù)腳本的幾率:團(tuán)隊(duì)大小和腳本數(shù)量。在這種情況下,重復(fù)腳本會(huì)創(chuàng)建不必要的HTTP請(qǐng)求,執(zhí)行無用的JavaScript代碼,而影響頁面性能。
IE會(huì)產(chǎn)生不必要的HTTP請(qǐng)求,而Firefox不會(huì)。在IE中,如果一個(gè)不可緩存的外部腳本被頁面引入了兩次,它會(huì)在頁面加載時(shí)產(chǎn)生兩個(gè)HTTP請(qǐng)求。即使腳本是可緩存的,在用戶重新加載頁面時(shí)也會(huì)產(chǎn)生額外的HTTP請(qǐng)求。
除了產(chǎn)生沒有意義的HTTP請(qǐng)求之外,多次對(duì)腳本求值也會(huì)浪費(fèi)時(shí)間。因?yàn)闊o論腳本是否可緩存,在Firefox和IE中都會(huì)執(zhí)行冗余的JavaScript代碼。
避免不小心把相同腳本引入兩次的一種方法就是在模版系統(tǒng)中實(shí)現(xiàn)腳本管理模塊。典型的腳本引入方法就是在HTML頁面中用SCRIPT標(biāo)簽:
16.盡量減少DOM訪問用JavaScript訪問DOM元素是很慢的,所以,為了讓頁面反應(yīng)更迅速,應(yīng)該:
緩存已訪問過的元素的索引
先“離線”更新節(jié)點(diǎn),再把它們添到DOM樹上
避免用JavaScript修復(fù)布局問題
17.用智能的事件處理器有時(shí)候感覺頁面反映不夠靈敏,是因?yàn)橛刑囝l繁執(zhí)行的事件處理器被添加到了DOM樹的不同元素上,這就是推薦使用事件委托的原因。如果一個(gè)div里面有10個(gè)按鈕,應(yīng)該只給div容器添加一個(gè)事件處理器,而不是給每個(gè)按鈕都添加一個(gè)。事件能夠冒泡,所以可以捕獲事件并得知哪個(gè)按鈕是事件源。
18.把腳本放在底部腳本會(huì)阻塞并行下載,HTTP/1.1官方文檔建議瀏覽器每個(gè)主機(jī)名下并行下載的組件數(shù)不要超過兩個(gè),如果圖片來自多個(gè)主機(jī)名,并行下載的數(shù)量就可以超過兩個(gè)。如果腳本正在下載,瀏覽器就不開始任何其它下載任務(wù),即使是在不同主機(jī)名下的。
有時(shí)候,并不容易把腳本移動(dòng)到底部。舉個(gè)例子,如果腳本是用document.write插入到頁面內(nèi)容中的,就沒辦法再往下移了。還可能存在作用域問題,在多數(shù)情況下,這些問題都是可以解決的。
一個(gè)常見的建議是用推遲(deferred)腳本,有DEFER屬性的腳本意味著不能含有document.write,并且提示瀏覽器告訴他們可以繼續(xù)渲染。不幸的是,F(xiàn)irefox不支持DEFER屬性。在IE中,腳本可能被推遲,但不盡如人意。如果腳本可以推遲,我們就可以把它放到頁面底部,頁面就可以更快地載入。
很多性能原則都是關(guān)于如何管理外部組件的,然而,在這些顧慮出現(xiàn)之前你應(yīng)該問一個(gè)更基礎(chǔ)的問題:應(yīng)該把JavaScript和CSS放到外部文件中還是直接寫在頁面里?
實(shí)際上,用外部文件可以讓頁面更快,因?yàn)镴avaScript和CSS文件會(huì)被緩存在瀏覽器。HTML文檔中的行內(nèi)JavaScript和CSS在每次請(qǐng)求該HTML文檔的時(shí)候都會(huì)重新下載。這樣做減少了所需的HTTP請(qǐng)求數(shù),但增加了HTML文檔的大小。另一方面,如果JavaScript和CSS在外部文件中,并且已經(jīng)被瀏覽器緩存起來了,那么我們就成功地把HTML文檔變小了,而且還沒有增加HTTP請(qǐng)求數(shù)。
20.壓縮JavaScript和CSS
壓縮具體來說就是從代碼中去除不必要的字符以減少大小,從而提升加載速度。代碼最小化就是去掉所有注釋和不必要的空白字符(空格,換行和tab)。在JavaScript中這樣做能夠提高響應(yīng)性能,因?yàn)橐螺d的文件變小了。兩個(gè)最常用的JavaScript代碼壓縮工具是JSMin和YUI Compressor,YUI compressor還可以壓縮CSS。
混淆是一種可選的源碼優(yōu)化措施,要比壓縮更復(fù)雜,所以混淆過程也更容易產(chǎn)生bug。在對(duì)美國前十的網(wǎng)站調(diào)查中,壓縮可以縮小21%,而混淆能縮小25%。雖然混淆的縮小程度更高,但比壓縮風(fēng)險(xiǎn)更大。
除了壓縮外部腳本和樣式,行內(nèi)的