前端性能優(yōu)化之從URL輸入到頁(yè)面加載過(guò)程分析
本文轉(zhuǎn)載自微信公眾號(hào)「前端萬(wàn)有引力」,作者一川 。轉(zhuǎn)載本文請(qǐng)聯(lián)系前端萬(wàn)有引力公眾號(hào)。
1寫在前面
在頁(yè)面加載到最終渲染顯示大致是這樣的:用戶在瀏覽器輸入U(xiǎn)RL回車后,瀏覽器為了將URL解析成IP地址,會(huì)向DNS服務(wù)器發(fā)起DNS查詢,獲取IP地址。在建立連接后,瀏覽器就可以發(fā)起HTTP請(qǐng)求,而服務(wù)器接受請(qǐng)求后進(jìn)行響應(yīng),瀏覽器從響應(yīng)結(jié)果中拿到數(shù)據(jù),并進(jìn)行解析和渲染,最后在用戶面前就出現(xiàn)了一個(gè)網(wǎng)頁(yè)。簡(jiǎn)而言之就是三個(gè)階段:
- 客戶端發(fā)起請(qǐng)求階段
- 服務(wù)端數(shù)據(jù)處理請(qǐng)求階段
- 客戶端頁(yè)面渲染階段
2客戶端請(qǐng)求階段的優(yōu)化點(diǎn)
客戶端發(fā)起請(qǐng)求階段是指用戶在瀏覽器輸入U(xiǎn)RL,經(jīng)過(guò)本地緩存確認(rèn)是否已經(jīng)存在這個(gè)網(wǎng)站。如果沒有,接著會(huì)由DNS查詢從域名服務(wù)器獲取這個(gè)IP地址,接下來(lái)就是客戶端通過(guò)TCP三次握手和TLS協(xié)商向服務(wù)器發(fā)起HTTP請(qǐng)求建立連接的過(guò)程。
本地緩存
本地緩存可以讓靜態(tài)資源加載更快,當(dāng)客戶端發(fā)起一個(gè)請(qǐng)求時(shí),靜態(tài)資源可以直接從客戶端獲取,不需要再想服務(wù)器請(qǐng)求。
但是在實(shí)際開發(fā)中,很多前端程序員會(huì)忽略本地緩存的優(yōu)化,這就會(huì)導(dǎo)致:在客戶端請(qǐng)求階段,假設(shè)一個(gè)項(xiàng)目的列表頁(yè)DNS產(chǎn)生時(shí)間是835ms,TCP三次握手和TLS協(xié)商是436ms,數(shù)據(jù)返回是412ms,這樣在強(qiáng)網(wǎng)條件下一個(gè)請(qǐng)求的響應(yīng)時(shí)間大概是1233ms。如果在弱網(wǎng)條件,一個(gè)請(qǐng)求連接的時(shí)間都需要2s,但是使用緩存處理的話,幾乎可以說(shuō)是幾ms內(nèi)完成請(qǐng)求。
強(qiáng)緩存:指的是瀏覽器在加載資源時(shí),根據(jù)請(qǐng)求頭的expires和cache-control判斷是否命中客戶端緩存。
協(xié)商緩存:指的是瀏覽器會(huì)先發(fā)送一個(gè)請(qǐng)求到服務(wù)器,通過(guò)last-modified和etag驗(yàn)證資源是否命中客戶端緩存。
DNS查詢
DNS之所以能夠成為前端性能的優(yōu)化點(diǎn),這是因?yàn)槊窟M(jìn)行一次DNS查詢,都要經(jīng)歷從客戶端到信號(hào)接收站,再到認(rèn)證DNS服務(wù)器的過(guò)程。
但是這樣每次查詢都要走這個(gè)流程就會(huì)耗費(fèi)很多的時(shí)間,優(yōu)化方法就是讓DNS查詢先緩存,而瀏覽器提供了DNS預(yù)獲取的接口,我們可以在打開瀏覽器或者Webview的同時(shí)就進(jìn)行配置。
HTTP請(qǐng)求
對(duì)于HTTP請(qǐng)求而言最大的優(yōu)化點(diǎn)在于請(qǐng)求阻塞,就是瀏覽器為了保證訪問速度,會(huì)默認(rèn)對(duì)同一域下的資源保持一定的連接數(shù),請(qǐng)求過(guò)多會(huì)進(jìn)行阻塞。對(duì)此我們提前做好域名規(guī)劃是很重要的,可以先看看當(dāng)前頁(yè)面需要用到哪些域名,最關(guān)鍵的是首屏中需要用到哪些域名。
域名散列:就是通過(guò)不同的域名,增加請(qǐng)求并行連接數(shù)。將靜態(tài)服務(wù)器地址pic.yichuan.com,做成支持pic0-5的6個(gè)域名,每次請(qǐng)求時(shí)隨機(jī)選取一個(gè)域名地址進(jìn)行請(qǐng)求,因?yàn)橛?個(gè)域名同時(shí)可用,最多可以進(jìn)行并行36個(gè)連接。
一次完整的HTTP請(qǐng)求需要經(jīng)歷DNS查找,建立TCP握手,瀏覽器發(fā)起HTTP請(qǐng)求,服務(wù)器接受請(qǐng)求并處理返回響應(yīng)結(jié)果,瀏覽器再接收響應(yīng)等過(guò)程。但是每一次HTTP請(qǐng)求都需要加載很多文件,建立連接并耗費(fèi)很多時(shí)間。如果有很多文件就需要發(fā)起很多次請(qǐng)求,而如果把若干個(gè)小文件合并成一個(gè)大文件就可以減少HTTP請(qǐng)求,減少訪問的時(shí)間、提升效率和速度。
3服務(wù)端數(shù)據(jù)處理階段的優(yōu)化點(diǎn)
服務(wù)端數(shù)據(jù)處理階段指的是WevServer接受到請(qǐng)求后,從數(shù)據(jù)存儲(chǔ)層取到數(shù)據(jù),再返回給前端的過(guò)程。服務(wù)端程序接受到HTTP請(qǐng)求后,會(huì)做一些請(qǐng)求參數(shù)處理以及權(quán)限校驗(yàn)。此過(guò)程的優(yōu)化點(diǎn):在于是否做了數(shù)據(jù)緩存處理、是否做了gzip壓縮以及是否具有重定向。gzip壓縮是一種壓縮技術(shù),通過(guò)gzip壓縮資源的下載速度會(huì)快很多,能夠大大提升頁(yè)面的展示速度。
數(shù)據(jù)緩存
在進(jìn)行數(shù)據(jù)緩存的幾種方法:
- 借助Service Worker的數(shù)據(jù)接口緩存
- 借助本地存儲(chǔ)的接口緩存
- CDN
Service Worker:是瀏覽器的一個(gè)高級(jí)屬性,本質(zhì)上是一個(gè)請(qǐng)求代理層,它存在的目的就是攔截和處理網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求。
借助本地存儲(chǔ)的接口緩存:指的是在一些對(duì)數(shù)據(jù)時(shí)效性要求不高的頁(yè)面,第一次請(qǐng)求到數(shù)據(jù)后,程序?qū)?shù)據(jù)存儲(chǔ)到本地存儲(chǔ)。下一次請(qǐng)求的時(shí)候,先去緩存里面取出數(shù)據(jù),如果沒有的話再想服務(wù)器發(fā)起請(qǐng)求。
CDN:基本思路是通過(guò)在網(wǎng)絡(luò)各處放置節(jié)點(diǎn)服務(wù)器,構(gòu)造一個(gè)智能虛擬網(wǎng)絡(luò),將用戶的請(qǐng)求導(dǎo)向離用戶最近的服務(wù)節(jié)點(diǎn)上。
為什么數(shù)據(jù)緩存會(huì)成為性能的優(yōu)化點(diǎn)呢?這是因?yàn)槊看握?qǐng)求數(shù)據(jù)接口,需要從客戶端到后端服務(wù)器再到更后端的數(shù)據(jù)存儲(chǔ)層,一層一層返回?cái)?shù)據(jù),最后再返回客戶端,這樣請(qǐng)求響應(yīng)的耗時(shí)很長(zhǎng)。
重定向
重定向是指網(wǎng)站資源遷移到其他位置后,用戶訪問站點(diǎn)時(shí),程序會(huì)自助將用戶請(qǐng)求從一個(gè)頁(yè)面轉(zhuǎn)移到另外一個(gè)頁(yè)面的過(guò)程。重定向的三種方式:
- 服務(wù)端發(fā)揮的302重定向
- META標(biāo)簽實(shí)現(xiàn)的重定向
- 前端Javascript通過(guò)window.location實(shí)現(xiàn)的重定向
它們都會(huì)引發(fā)新的DNS查詢,會(huì)導(dǎo)致新的TCP三次握手和TLS協(xié)商以及產(chǎn)生新的HTTP請(qǐng)求,而這些都會(huì)導(dǎo)致請(qǐng)求過(guò)程中更多地時(shí)間,進(jìn)而影響前端性能。
當(dāng)前服務(wù)端對(duì)數(shù)據(jù)加工聚合處理后,客戶端拿到數(shù)據(jù),接下來(lái)會(huì)進(jìn)入解析和渲染階段。解析階段就是HTML解析器將頁(yè)面內(nèi)容轉(zhuǎn)換成DOM樹和CSSDOM樹的過(guò)程。所謂DOM樹,就是文檔對(duì)象模型(Document Object Model),它描述了標(biāo)簽之間的層次和結(jié)構(gòu)。CSSDOM樹,即CSS對(duì)象模型,主要描述了樣式集的層次和結(jié)構(gòu)。
CSS解析器遍歷其中每個(gè)規(guī)則,將CSS規(guī)則解析瀏覽器可解析和處理的樣式集合,最終結(jié)合瀏覽器里面的默認(rèn)樣式,匯總形成具有父子關(guān)系的CSSDOM樹。
4頁(yè)面解析和渲染階段的優(yōu)化點(diǎn)
主線程會(huì)計(jì)算DOM節(jié)點(diǎn)的最終樣式,生成布局樹,布局樹會(huì)記錄參與頁(yè)面布局的節(jié)點(diǎn)和樣式。
DOM樹解析中的優(yōu)化點(diǎn)
解析和渲染階段的流程環(huán)節(jié)比較多,邏輯復(fù)雜,優(yōu)化點(diǎn)也比較多,比如:DOM樹構(gòu)建過(guò)程,CSSDOM樹生成階段,重排和重繪過(guò)程等。
- 當(dāng)HTML標(biāo)簽不滿足web語(yǔ)義化時(shí),瀏覽器就需要更多時(shí)間去解析DOM標(biāo)簽的含義。
- DOM節(jié)點(diǎn)的數(shù)量越多,構(gòu)建DOM樹的時(shí)間就越長(zhǎng),進(jìn)而延長(zhǎng)解析時(shí)間,拖延頁(yè)面展示速度。
- 文檔中包含<script>標(biāo)簽時(shí),無(wú)論是DOM或者是CSSDOM都可以被Javscript所訪問和修改,所以一旦在頁(yè)面解析時(shí)遇到<script>標(biāo)簽,DOM的構(gòu)造過(guò)程就會(huì)暫停。因此外部<script>標(biāo)簽常被稱為”解析“階段的攔路虎,有時(shí)就因?yàn)榻馕鲞^(guò)程中多了一個(gè)<script>標(biāo)簽造成頁(yè)面解析階段從200ms到1s。對(duì)此,外部腳本的加載時(shí)機(jī)一定要明確好,能夠延遲加載就選用延遲加載,通過(guò)使用defer和async告知瀏覽器在等待腳本下載期間不阻止解析過(guò)程。
CSS執(zhí)行會(huì)阻塞渲染,阻止JS執(zhí)行,而JS加載和執(zhí)行會(huì)阻塞HTML解析,阻止CSSDOM構(gòu)建。如果這些CSS、JS標(biāo)簽放在<head>標(biāo)簽中,并且需要加載和解析很久的話,那么頁(yè)面就出顯現(xiàn)白屏情況。因此,JS文件要放在底部(不會(huì)阻止DOM解析,但是會(huì)阻塞渲染),等HTML解析后再加載JS文件,盡早向用戶呈現(xiàn)頁(yè)面的內(nèi)容。
之所以要講CSS文件放在頭部,這是因?yàn)榧虞dHTML后再加載CSS,會(huì)讓用戶第一時(shí)間看到?jīng)]有樣式的頁(yè)面,為了避免出現(xiàn)這種情況需要將CSS文件放在頭部。當(dāng)然JS文件也可以放在頭部,但是需要在<script>標(biāo)簽加上defer屬性就可以了,異步進(jìn)行下載、延遲執(zhí)行。
布局中的優(yōu)化點(diǎn)
瀏覽器會(huì)根據(jù)樣式解析器給出的樣式規(guī)則,來(lái)計(jì)算某個(gè)元素需要占據(jù)的空間大小和屏幕中的位置,借助計(jì)算結(jié)果來(lái)進(jìn)行布局。而主線程布局是采用的流布局,就是從上到下、從左到右進(jìn)行遍歷進(jìn)行布局。
假設(shè)我們?cè)陧?yè)面渲染過(guò)程運(yùn)行時(shí)修改了一個(gè)元素的屬性,這時(shí)布局階段受到了影響,瀏覽器必須檢查所有其他區(qū)域的元素,然后自動(dòng)重排頁(yè)面,相當(dāng)于進(jìn)行了一遍整個(gè)渲染流程。
此外,因?yàn)闉g覽器每次布局計(jì)算都要作用于真?zhèn)€DOM,如果元素量大,計(jì)算出所有的元素位置和尺寸會(huì)花費(fèi)很長(zhǎng)的時(shí)間,所以布局階段很容易成為性能瓶頸點(diǎn),需要我們進(jìn)行優(yōu)化。
比如說(shuō):當(dāng)你做列表頁(yè)性能優(yōu)化時(shí),開始布局時(shí)并沒有確定列表頁(yè)圖片的初始尺寸,只設(shè)定了一個(gè)基礎(chǔ)的占位尺寸。那么當(dāng)圖片加載完畢后,主線程才知道圖片的大小,不得不重新進(jìn)行布局計(jì)算,然后再次進(jìn)行頁(yè)面渲染。
5參考文章
《前端性能優(yōu)化方法與實(shí)踐》
6寫在最后
頁(yè)面加載全過(guò)程很復(fù)雜,內(nèi)容也比較多,能夠進(jìn)行優(yōu)化點(diǎn)也是眾多,而本篇文章只是簡(jiǎn)單介紹了前端領(lǐng)域的可優(yōu)化點(diǎn)。對(duì)于偏硬件領(lǐng)域能夠做的優(yōu)化點(diǎn)有GPU繪圖、操作系統(tǒng)GUI和LCD顯示等;對(duì)于計(jì)算機(jī)網(wǎng)絡(luò)中的網(wǎng)絡(luò)層和服務(wù)層,比如擁塞預(yù)防、負(fù)載均衡和慢啟動(dòng);還有一些頁(yè)面的解析和渲染算法,比如解析算法、標(biāo)記算法和樹構(gòu)建算法等。