記得面試現在這份工作的時候,一位領導語重心長地談道——當今的世界是網際網路的世界,IT企業之間的競爭是很激烈的,如果一個網頁的載入和顯示速度,相比別人的站點頁面有那麼0.1秒的提升,那也是很大的一個成就。
然後我不知道怎麼寫下去了,就在群裡問了那群狗頭軍師,結果是這樣的。。。
好的,是時候“語鋒一轉”切回主題了 —— 如何提升我們站點頁面的訪問速度、減少等待時間,從而最大化地提升使用者訪問體驗呢?
針對這個問題,我們今天會從前端的角度來提出系列解決方案,它們都能有效地提升你頁面的訪問速度。
一. 減少對伺服器的檔案請求
常規的HTTP請求屬於“請求”-“應答”-“斷開”形式的短連線,每一個獨立的資源我們都會向伺服器發去一份get請求,再等服務端將我們需要的檔案傳回來。每一次資源的請求都實實在在地耗費了一次“連線-等待-接收”的時間(當然將http請求設為keep-alive長連線狀態可以減少“連線”的次數和時間),如果我們能有效減少對伺服器檔案的請求次數,便意味著我們可以從這塊省下一些頁面等待時間,也可以順便減少伺服器的負擔。
對於這個解決方案,我們可以這麼做:
1. 使用css sprite技術合併多個圖片為單個圖片檔案,實際使用時透過background-position來定位背景位置(相信大家第一個想到的也是這個吧);
2. 合併多個css樣式檔案為單個樣式檔案,合併多個指令碼為單個指令碼,再在頁面中取用合併後的樣式/指令碼檔案。對於這個你可以使用r.js來幫忙,但我個人倒是不怎麼推薦這個方法,因為合併了檔案之後,多個頁面之間公共部分的樣式/指令碼檔案就無法快取到客戶端了;
3. 使用base64編碼來展示圖片。就如圖github 404頁面那樣:
你可以使用這個工具來幫你把圖片轉換為base64編碼的檔案流,但常規只推薦你把這種方式使用在使用者重覆訪問量較少的頁面,因為它們雖然無須從服務端get一遍,但也無法快取在客戶端,導致用戶每次訪問頁面都要重新渲染一次。而且冗長的檔案流程式碼會佔用你頁面很大的程式碼空間,維護起頁面來估計也會挺心塞;
4. 將小塊的css、js程式碼段直接寫在頁面上,而非在頁面引入獨立的樣式/指令碼檔案。相信有的朋友看慣了“保持結構 (標記)、表現 (樣式)、行為 (指令碼)三者分離”的規範,對此觀點可能有些意見。只能說規範不是教條,適合自己的才是硬道理。直接把小段的、復用率低的樣式/指令碼直接寫於頁面上帶來的利還是大於弊的(弊可能也就是增大了頁面程式碼量、不那麼好維護了點)。反觀所有主流入口網站的頁面源檔案,基本沒有一個是把樣式/指令碼都全部作為外部檔案引入的(無論他們是否從減少伺服器請求這點出發,事實都是這樣);
5. 利用http-equiv=”expires”元標簽,設定一個未來的某時間點作為頁面檔案過期時間,使用者在過期時間之前所獲取到的頁面檔案都僅從快取中去取。不過這個辦法太死板(有時候即使服務端及時把過期時間更改為已結束時間,客戶端可能都不會按照新更改的規則去服務端獲取新檔案資源),常規是不推薦使用的。
二. 減少檔案大小
檔案太大(特別是圖片)導致載入時間較長,往往都是影響頁面載入體驗的頭號大敵,那麼盡可能減少請求檔案的大小便是相當重要的事情了,我們可以做的事情有:
1. 壓縮樣式/指令碼檔案,就此你可以使用gulp或者grunt來實現這點,它們均能很好地減少css/js檔案的大小(對於js還能起到混淆變數、函式名的作用);
2. 針對性選擇圖片格式,在無透明背景需求下,對於顏色較單一、無色彩漸變的圖片僅使用gif格式,對於jpg圖片也可按照其清晰度要求,在匯出jpg的時候選擇對應的“品質”進行最佳化:
如果你喜歡嘗鮮,可以學淘寶那樣使用webp圖片格式,它能很好地最佳化同畫質下的檔案大小:
如果你對webp感興趣,可以查閱這裡;
3. 使用Font Awesome來替代頁面上的圖示,其原理是使用@font-face讓使用者下載一個非常小的UI字型包,把頁面上用到的圖示以字元的形式來顯示,從而減少了圖片需求和圖示檔案大小。
三. 適度使用CDN
使用CDN有幾個好處:如果使用者在其它站點下載過這個CDN資源,那麼來我們站點僅僅從快取獲取即可;減少了對自己站點伺服器的檔案請求(外部CDN的情況下),減少伺服器負擔;多個域會使瀏覽器允許非同步下載資源的最大數量增多,比如一個站點只從一個域來請求資源,那麼FireFox只允許同時刻最多非同步下載2個檔案,但如果使用了外部CDN來引入資源,那麼FF允許在同時非同步下載本域中的兩個資源外,還額外允許同時非同步下載另一個域(CDN)下的2個資源。
但是使用CDN有一個很大的問題——增加了dns解析的開銷,如果一個頁面同時引入了多個CDN的資源,可能會因為dns解析而陷入較多的等待時間,導致得不償失。
對於這個問題,常規是建議一個站點下只使用同一個可靠、快速的CDN來引入各種所需資源即可,也就是說,建議一個頁面從2個不同的域(比如站點域和CDN域)下來請求資源是最佳的選擇(據說這個結論是雅虎前端工程師提出的,這裡有一篇很不錯的文章)。
四. 延遲請求、非同步載入指令碼
在各主流瀏覽器下,常規情況,我們的指令碼檔案跟隨其它資源檔案一樣都是非同步下載的,但這裡存在一個問題——比如FireFox下載好指令碼後的一小段時間內會有“執行阻塞”的情況發生,也就是說瀏覽器下載好指令碼後執行它的這段時間裡,瀏覽器的其它行為被阻塞,導致頁面上的其它資源都是無法被請求和下載的:
如果你頁面裡存在js程式碼執行時間過長的情況,那麼使用者就會明顯感覺到頁面的延遲。解決這個問題有一個簡單的方法——將指令碼請求標簽放置到
結束標簽前,使得頁面上的指令碼成為最後被請求的資源,自然也不會阻塞其它頁面資源的請求事件了。
另外,雖然上面提到“我們的指令碼檔案跟隨其它資源檔案一樣都是非同步下載的”,但非同步下載不代表非同步執行,為了嚴格保證指令碼邏輯順序和依賴關係的正確性,瀏覽器會按照指令碼被請求的先後順序來執行指令碼。那麼問題就來了——如果頁面上的指令碼依賴關係並不大,甚至沒有任何相互間的依賴,那麼瀏覽器的這套規則就僅僅增加了頁面請求阻塞時間而已(就像你花大錢買了一筆保險,但被保險期間你平安無事啥都沒發生。。。嗯,這個比喻有點反人類。。。)。
解決這個問題的辦法無非就是讓指令碼無阻塞地非同步執行,比如給script標簽加上defer和async屬性或者動態註入指令碼(可以參考這裡),但這些都不是良好的解決方案,要麼存在相容性問題,要麼太麻煩還無法處理依賴。
個人是推薦使用 requireJS(AMD規範) 或 seaJS(CMD規範) 來非同步載入指令碼並處理模組依賴的,前者將“依賴前置”(預載入所有被依賴指令碼模組,執行速度最快),後者走的“依賴就近”(懶載入被依賴指令碼模組,請求指令碼更科學),你可以根據專案具體需求來選擇最合適的。
五. 延遲請求首屏外的檔案
先解釋下,“首屏”指的是頁面初始化時候的頁面內容顯示區域,也就是頁面一載入,使用者就首先看到的區域。
比如像京東啊淘寶啊,對於需要滾動頁面才能看到的圖片內容,都做了類似lazyload的處理,這些無非都是走了代理樣式的理念,但的確給使用者一個錯覺——這個頁面更快地載入完了,因為我很快就看到了螢幕上的內容(即使我還沒下拉捲軸,而頁面後方的檔案其實還沒真正載入呢)。
我們可以這樣實現此方案,不依賴任何lazyload庫,拿圖片來做示範,我們可以這樣編寫首屏外的圖片(假設某張圖片地址是a.jpg)的img標簽:
如上所示,頁面初步載入這張圖片的時候是直接以base64的方式(當然你也可以統一使用一張佔位圖loading.gif來替代)來快速顯示一張極小的圖片的,而圖片本身的真實路徑是存在data-src屬性內的,我們可以在頁面載入結束後再向伺服器請求它真實的檔案並替換:
function init() {
var imgDefer = document.getElementsByTagName(‘img’);
for (var i=0; i
if(imgDefer[i].getAttribute(‘data-src’)) {
imgDefer[i].setAttribute(‘src’,imgDefer[i].getAttribute(‘data-src’));
}
}
}
window.onload = init;
如上是對圖片的延遲載入處理,對於影片、音訊檔案,可以採取完全一樣的原理來延遲載入,從而有效減少頁面初始化等待時間。
六. 最佳化頁面模組排放順序
這裡有一個很好的例子,比如有一個頁面是這樣的——左邊是側邊欄,用於存放使用者的頭像啊、資料啊,以及網站投放的廣告啊,而右側是文章內容區域:
那麼我們的程式碼很可能是這樣的:
於是乎,瀏覽器按照它的UI單執行緒準則從上到下先載入了側邊欄,再載入我們的文章。。。
很明顯,這樣不是一個人性化的載入順序,我們得弄清楚,頁面上各個區域模組,對於使用者而言,哪個才是最重要、最應當首先展示的。
對於上面的例子,文章內容才應該是使用者首先要看到、需要瀏覽器優先請求和顯示的區域。所以我們得修改我們的程式碼為:
當然這裡僅僅是用一個小示例來挑起各位的腦洞,懂得舉一反三和實際運用才是硬道理。
七. 其它建議
1. 不要在css中使用@import,它會讓一個樣式檔案去等待另一個樣式檔案的請求,無形中增加了頁面等待時間(當然如果走的scss,@import就是另一回事了,呃跑題了~);
2. 避免頁面或者頁面檔案重定向查詢,這相當於你走進了一間衛生間,然後看到上面的牌子說“此處不同,請去前面左拐的衛生間”,又得重走一遍;
3. 減少無效請求——比如透過css/js來請求一個不存在的資源,可能會導致較長的等待和阻塞(直到它傳回錯誤資訊);
4. 無論你是否決定將指令碼放到頁尾,但一定要保障指令碼放置於樣式檔案後方;
5. 檔案在小於50K的時候,直接讀取檔案流會比從檔案系統中去讀取檔案來的快些,大於50K則相反。比如有一張圖片,如果它小於50K,我們可以將它轉為二進位制資料儲存在資料庫中,頁面若要讀取該圖片則從資料庫上來讀取,若檔案大小大於50K,那建議存放在可訪問的檔案夾中以檔案的形式來讀取即可;
6. 使用 cookie-free domains 來存放資源,減少無用cookie傳輸的網路開銷(可以參考這篇文章);
7. 配置.htaccess檔案、走Gzip頁面壓縮形式、開啟keep-alive連線樣式等後端解決方案,這邊就不細說了。
相信頁面提速的方案絕對不僅僅侷限於上面提到的這些,而且每個專案都有著各種需求和情況,只能按需選擇適合自己的方案。
但最重要的還是——儘量把使用者的體驗放在第一位,無論是頁面的載入或者互動,都應當多從使用者角度切入去思考和設計最優選的方案,這樣相信你一定能做出出色、受歡迎的作品。
今天聊的就是這些,共勉~
原文出處:VaJoy Larn 的部落格(@VaJoy_學霸樣式啟動)