java web 页面提速
記得面試現(xiàn)在這份工作的時(shí)候,一位領(lǐng)導(dǎo)語(yǔ)重心長(zhǎng)地談道——當(dāng)今的世界是互聯(lián)網(wǎng)的世界,IT企業(yè)之間的競(jìng)爭(zhēng)是很激烈的,如果一個(gè)網(wǎng)頁(yè)的加載和顯示速度,相比別人的站點(diǎn)頁(yè)面有那么0.1秒的提升,那也是很大的一個(gè)成就。
然后我不知道怎么寫(xiě)下去了,就在群里問(wèn)了那群狗頭軍師,結(jié)果是這樣的。。。
好的,是時(shí)候“語(yǔ)鋒一轉(zhuǎn)”切回主題了 —— 如何提升我們站點(diǎn)頁(yè)面的訪問(wèn)速度、減少等待時(shí)間,從而最大化地提升用戶(hù)訪問(wèn)體驗(yàn)?zāi)?#xff1f;
針對(duì)這個(gè)問(wèn)題,我們今天會(huì)從前端的角度來(lái)提出系列解決方案,它們都能有效地提升你頁(yè)面的訪問(wèn)速度。
一. 減少對(duì)服務(wù)器的文件請(qǐng)求
常規(guī)的HTTP請(qǐng)求屬于“請(qǐng)求”-“應(yīng)答”-“斷開(kāi)”形式的短連接,每一個(gè)獨(dú)立的資源我們都會(huì)向服務(wù)器發(fā)去一份get請(qǐng)求,再等服務(wù)端將我們需要的文件傳回來(lái)。每一次資源的請(qǐng)求都實(shí)實(shí)在在地耗費(fèi)了一次“連接-等待-接收”的時(shí)間(當(dāng)然將http請(qǐng)求設(shè)為keep-alive長(zhǎng)連接狀態(tài)可以減少“連接”的次數(shù)和時(shí)間),如果我們能有效減少對(duì)服務(wù)器文件的請(qǐng)求次數(shù),便意味著我們可以從這塊省下一些頁(yè)面等待時(shí)間,也可以順便減少服務(wù)器的負(fù)擔(dān)。
對(duì)于這個(gè)解決方案,我們可以這么做:
1. 使用css sprite技術(shù)合并多個(gè)圖片為單個(gè)圖片文件,實(shí)際使用時(shí)通過(guò)background-position來(lái)定位背景位置(相信大家第一個(gè)想到的也是這個(gè)吧);
2. 合并多個(gè)css樣式文件為單個(gè)樣式文件,合并多個(gè)腳本為單個(gè)腳本,再在頁(yè)面中引用合并后的樣式/腳本文件。對(duì)于這個(gè)你可以使用r.js來(lái)幫忙(看我這篇文章),但我個(gè)人倒是不怎么推薦這個(gè)方法,因?yàn)楹喜⒘宋募?#xff0c;多個(gè)頁(yè)面之間公共部分的樣式/腳本文件就無(wú)法緩存到客戶(hù)端了;
3. 使用base64編碼來(lái)展示圖片。就如圖github 404頁(yè)面那樣:
你可以使用這個(gè)工具來(lái)幫你把圖片轉(zhuǎn)換為base64編碼的文件流,但常規(guī)只推薦你把這種方式使用在用戶(hù)重復(fù)訪問(wèn)量較少的頁(yè)面,因?yàn)樗鼈冸m然無(wú)須從服務(wù)端get一遍,但也無(wú)法緩存在客戶(hù)端,導(dǎo)致用戶(hù)每次訪問(wèn)頁(yè)面都要重新渲染一次。而且冗長(zhǎng)的文件流代碼會(huì)占用你頁(yè)面很大的代碼空間,維護(hù)起頁(yè)面來(lái)估計(jì)也會(huì)挺心塞;
4. 將小塊的css、js代碼段直接寫(xiě)在頁(yè)面上,而非在頁(yè)面引入獨(dú)立的樣式/腳本文件。相信有的朋友看慣了“保持結(jié)構(gòu) (標(biāo)記)、表現(xiàn) (樣式)、行為 (腳本)三者分離”的規(guī)范,對(duì)此觀點(diǎn)可能有些意見(jiàn)。只能說(shuō)規(guī)范不是教條,適合自己的才是硬道理。直接把小段的、復(fù)用率低的樣式/腳本直接寫(xiě)于頁(yè)面上帶來(lái)的利還是大于弊的(弊可能也就是增大了頁(yè)面代碼量、不那么好維護(hù)了點(diǎn))。反觀所有主流門(mén)戶(hù)網(wǎng)站的頁(yè)面源文件,基本沒(méi)有一個(gè)是把樣式/腳本都全部作為外部文件引入的(無(wú)論他們是否從減少服務(wù)器請(qǐng)求這點(diǎn)出發(fā),事實(shí)都是這樣),配合gzip壓縮會(huì)是很好的選擇;
5. 利用http-equiv="expires"元標(biāo)簽,設(shè)定一個(gè)未來(lái)的某時(shí)間點(diǎn)作為頁(yè)面文件過(guò)期時(shí)間,用戶(hù)在過(guò)期時(shí)間之前所獲取到的頁(yè)面文件都僅從緩存中去取。最好的形式是給資源名稱(chēng)加md5之類(lèi)的唯一標(biāo)識(shí)符后綴,然后設(shè)置一段較長(zhǎng)的過(guò)期時(shí)間,同時(shí)請(qǐng)后端配置好ETag。這塊的內(nèi)容可以參考這篇文章。
二. 減少文件大小
文件太大(特別是圖片)導(dǎo)致加載時(shí)間較長(zhǎng),往往都是影響頁(yè)面加載體驗(yàn)的頭號(hào)大敵,那么盡可能減少請(qǐng)求文件的大小便是相當(dāng)重要的事情了,我們可以做的事情有:
1. 壓縮樣式/腳本文件,就此你可以使用gulp或者grunt來(lái)實(shí)現(xiàn)這點(diǎn),它們均能很好地減少css/js文件的大小(對(duì)于js還能起到混淆變量、函數(shù)名的作用);
2. 針對(duì)性選擇圖片格式,在無(wú)透明背景需求下,對(duì)于顏色較單一、無(wú)色彩漸變的圖片僅使用gif格式,對(duì)于jpg圖片也可按照其清晰度要求,在導(dǎo)出jpg的時(shí)候選擇對(duì)應(yīng)的“品質(zhì)”進(jìn)行優(yōu)化:
另外,你可以使用TinyPNG或者騰訊智圖這樣的工具來(lái)壓縮圖片(極力推薦)。
如果你喜歡嘗鮮,可以學(xué)淘寶那樣使用webp圖片格式,它能很好地優(yōu)化同畫(huà)質(zhì)下的文件大小:
如果你對(duì)webp感興趣,可以查閱這里;
3. 使用Font Awesome來(lái)替代頁(yè)面上的圖標(biāo),其原理是使用@font-face讓用戶(hù)下載一個(gè)非常小的UI字體包,把頁(yè)面上用到的圖標(biāo)以字符的形式來(lái)顯示,從而減少了圖片需求和圖標(biāo)文件大小。
三. 適度使用CDN
使用CDN有幾個(gè)好處:如果用戶(hù)在其它站點(diǎn)下載過(guò)這個(gè)CDN資源,那么來(lái)我們站點(diǎn)僅僅從緩存獲取即可;減少了對(duì)自己站點(diǎn)服務(wù)器的文件請(qǐng)求(外部CDN的情況下),減少服務(wù)器負(fù)擔(dān);多個(gè)域會(huì)使瀏覽器允許異步下載資源的最大數(shù)量增多,比如一個(gè)站點(diǎn)只從一個(gè)域來(lái)請(qǐng)求資源,那么FireFox只允許同時(shí)刻最多異步下載2個(gè)文件,但如果使用了外部CDN來(lái)引入資源,那么FF允許在同時(shí)異步下載本域中的兩個(gè)資源外,還額外允許同時(shí)異步下載另一個(gè)域(CDN)下的2個(gè)資源。
但是使用CDN有一個(gè)很大的問(wèn)題——增加了dns解析的開(kāi)銷(xiāo),如果一個(gè)頁(yè)面同時(shí)引入了多個(gè)CDN的資源,可能會(huì)因?yàn)閐ns解析而陷入較多的等待時(shí)間,導(dǎo)致得不償失。
對(duì)于這個(gè)問(wèn)題,常規(guī)是建議一個(gè)站點(diǎn)下只使用同一個(gè)可靠、快速的CDN來(lái)引入各種所需資源即可,也就是說(shuō),建議一個(gè)頁(yè)面從2個(gè)不同的域(比如站點(diǎn)域和CDN域)下來(lái)請(qǐng)求資源是最佳的選擇(據(jù)說(shuō)這個(gè)結(jié)論是雅虎前端工程師提出的,這里有一篇很不錯(cuò)的文章)。
四. 延遲請(qǐng)求、異步加載腳本
在各主流瀏覽器下,常規(guī)情況,我們的腳本文件跟隨其它資源文件一樣都是異步下載的,但這里存在一個(gè)問(wèn)題——比如FireFox下載好腳本后的一小段時(shí)間內(nèi)會(huì)有“執(zhí)行阻塞”的情況發(fā)生,也就是說(shuō)瀏覽器下載好腳本后執(zhí)行它的這段時(shí)間里,瀏覽器的其它行為被阻塞,導(dǎo)致頁(yè)面上的其它資源都是無(wú)法被請(qǐng)求和下載的:
如果你頁(yè)面里存在js代碼執(zhí)行時(shí)間過(guò)長(zhǎng)的情況,那么用戶(hù)就會(huì)明顯感覺(jué)到頁(yè)面的延遲。解決這個(gè)問(wèn)題有一個(gè)簡(jiǎn)單的方法——將腳本請(qǐng)求標(biāo)簽放置到</body>結(jié)束標(biāo)簽前,使得頁(yè)面上的腳本成為最后被請(qǐng)求的資源,自然也不會(huì)阻塞其它頁(yè)面資源的請(qǐng)求事件了。
另外,雖然上面提到“我們的腳本文件跟隨其它資源文件一樣都是異步下載的”,但異步下載不代表異步執(zhí)行,為了嚴(yán)格保證腳本邏輯順序和依賴(lài)關(guān)系的正確性,瀏覽器會(huì)按照腳本被請(qǐng)求的先后順序來(lái)執(zhí)行腳本。那么問(wèn)題就來(lái)了——如果頁(yè)面上的腳本依賴(lài)關(guān)系并不大,甚至沒(méi)有任何相互間的依賴(lài),那么瀏覽器的這套規(guī)則就僅僅增加了頁(yè)面請(qǐng)求阻塞時(shí)間而已(就像你花大錢(qián)買(mǎi)了一筆保險(xiǎn),但被保險(xiǎn)期間你平安無(wú)事啥都沒(méi)發(fā)生。。。嗯,這個(gè)比喻有點(diǎn)反人類(lèi)。。。)。
解決這個(gè)問(wèn)題的辦法無(wú)非就是讓腳本無(wú)阻塞地異步執(zhí)行,比如給script標(biāo)簽加上defer和async屬性或者動(dòng)態(tài)注入腳本(可以參考這里),但這些都不是良好的解決方案,要么存在兼容性問(wèn)題,要么太麻煩還無(wú)法處理依賴(lài)。
個(gè)人是推薦使用 requireJS(AMD規(guī)范) 或 seaJS(CMD規(guī)范) 來(lái)異步加載腳本并處理模塊依賴(lài)的,前者將“依賴(lài)前置”(預(yù)加載所有被依賴(lài)腳本模塊,執(zhí)行速度最快),后者走的“依賴(lài)就近”(懶加載被依賴(lài)腳本模塊,請(qǐng)求腳本更科學(xué)),你可以根據(jù)項(xiàng)目具體需求來(lái)選擇最合適的。
五. 延遲請(qǐng)求首屏外的文件
先解釋下,“首屏”指的是頁(yè)面初始化時(shí)候的頁(yè)面內(nèi)容顯示區(qū)域,也就是頁(yè)面一加載,用戶(hù)就首先看到的區(qū)域。
比如像京東啊淘寶啊,對(duì)于需要滾動(dòng)頁(yè)面才能看到的圖片內(nèi)容,都做了類(lèi)似lazyload的處理,這些無(wú)非都是走了代理模式的理念,但的確給用戶(hù)一個(gè)錯(cuò)覺(jué)——這個(gè)頁(yè)面更快地加載完了,因?yàn)槲液芸炀涂吹搅似聊簧系膬?nèi)容(即使我還沒(méi)下拉滾動(dòng)條,而頁(yè)面后方的文件其實(shí)還沒(méi)真正加載呢)。
我們可以這樣實(shí)現(xiàn)此方案,不依賴(lài)任何lazyload庫(kù),拿圖片來(lái)做示范,我們可以這樣編寫(xiě)首屏外的圖片(假設(shè)某張圖片地址是a.jpg)的img標(biāo)簽:
<img src="https://img-blog.csdnimg.cn/2022010702091364327.png" data-src="a.jpg">如上所示,頁(yè)面初步加載這張圖片的時(shí)候是直接以base64的方式(當(dāng)然你也可以統(tǒng)一使用一張占位圖loading.gif來(lái)替代)來(lái)快速顯示一張極小的圖片的,而圖片本身的真實(shí)路徑是存在data-src屬性?xún)?nèi)的,我們可以在頁(yè)面加載結(jié)束后再向服務(wù)器請(qǐng)求它真實(shí)的文件并替換:
function init() {var imgDefer = document.getElementsByTagName('img');for (var i=0; i<imgDefer.length; i++) {if(imgDefer[i].getAttribute('data-src')) {imgDefer[i].setAttribute('src',imgDefer[i].getAttribute('data-src'));} } } window.onload = init;如上是對(duì)圖片的延遲加載處理,對(duì)于視頻、音頻文件,可以采取完全一樣的原理來(lái)延遲加載,從而有效減少頁(yè)面初始化等待時(shí)間。
六. 優(yōu)化頁(yè)面模塊排放順序
這里有一個(gè)很好的例子,比如有一個(gè)頁(yè)面是這樣的——左邊是側(cè)邊欄,用于存放用戶(hù)的頭像啊、資料啊,以及網(wǎng)站投放的廣告啊,而右側(cè)是文章內(nèi)容區(qū)域:
那么我們的代碼很可能是這樣的:
<body><sidebar><!-- 側(cè)邊欄內(nèi)容 --></sidebar><content><!-- 文章內(nèi)容 --></content> </body>于是乎,瀏覽器按照它的UI單線程準(zhǔn)則從上到下先加載了側(cè)邊欄,再加載我們的文章。。。
很明顯,這樣不是一個(gè)人性化的加載順序,我們得弄清楚,頁(yè)面上各個(gè)區(qū)域模塊,對(duì)于用戶(hù)而言,哪個(gè)才是最重要、最應(yīng)當(dāng)首先展示的。
對(duì)于上面的例子,文章內(nèi)容才應(yīng)該是用戶(hù)首先要看到、需要瀏覽器優(yōu)先請(qǐng)求和顯示的區(qū)域。所以我們得修改我們的代碼為:
<body><content><!-- 文章內(nèi)容 --></content><sidebar><!-- 側(cè)邊欄內(nèi)容 --></sidebar> </body>當(dāng)然這里僅僅是用一個(gè)小示例來(lái)挑起各位的腦洞,懂得舉一反三和實(shí)際運(yùn)用才是硬道理。
七. 其它建議
1. 不要在css中使用@import,它會(huì)讓一個(gè)樣式文件去等待另一個(gè)樣式文件的請(qǐng)求,無(wú)形中增加了頁(yè)面等待時(shí)間(當(dāng)然如果走的scss,@import就是另一回事了,呃跑題了~);
2. 避免頁(yè)面或者頁(yè)面文件重定向查找,這相當(dāng)于你走進(jìn)了一間衛(wèi)生間,然后看到上面的牌子說(shuō)“此處不同,請(qǐng)去前面左拐的衛(wèi)生間”,又得重走一遍;
3. 減少無(wú)效請(qǐng)求——比如通過(guò)css/js來(lái)請(qǐng)求一個(gè)不存在的資源,可能會(huì)導(dǎo)致較長(zhǎng)的等待和阻塞(直到它返回錯(cuò)誤信息);
4. 無(wú)論你是否決定將腳本放到頁(yè)尾,但一定要保障腳本放置于樣式文件后方;
5. 文件在小于50K的時(shí)候,直接讀取文件流會(huì)比從文件系統(tǒng)中去讀取文件來(lái)的快些,大于50K則相反。比如有一張圖片,如果它小于50K,我們可以將它轉(zhuǎn)為二進(jìn)制數(shù)據(jù)存儲(chǔ)在數(shù)據(jù)庫(kù)中,頁(yè)面若要讀取該圖片則從數(shù)據(jù)庫(kù)上來(lái)讀取,若文件大小大于50K,那建議存放在可訪問(wèn)的文件夾中以文件的形式來(lái)讀取即可;
6. 使用 cookie-free domains 來(lái)存放資源,減少無(wú)用cookie傳輸?shù)木W(wǎng)絡(luò)開(kāi)銷(xiāo)(可以參考這篇文章);
7. 不重要的樣式文件可以走無(wú)阻塞渲染的加載形式,可參考這篇文章;
8. 配置.htaccess文件、走Gzip頁(yè)面壓縮形式、開(kāi)啟keep-alive連接模式等后端解決方案,這邊就不細(xì)說(shuō)了。
相信頁(yè)面提速的方案絕對(duì)不僅僅局限于上面提到的這些,而且每個(gè)項(xiàng)目都有著各種需求和情況,只能按需選擇適合自己的方案。
但最重要的還是——盡量把用戶(hù)的體驗(yàn)放在第一位,無(wú)論是頁(yè)面的加載或者交互,都應(yīng)當(dāng)多從用戶(hù)角度切入去思考和設(shè)計(jì)最優(yōu)選的方案,這樣相信你一定能做出出色、受歡迎的作品。
今天聊的就是這些,共勉~
?
轉(zhuǎn)載于:https://www.cnblogs.com/yelongsan/p/6292960.html
總結(jié)
以上是生活随笔為你收集整理的java web 页面提速的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。