一般情況下,如果網(wǎng)頁加載時(shí)間超過5s,用戶就會(huì)感覺頁面比較卡,遇到耐心不好的用戶,肯定就直接關(guān)閉走人了,所以加載的時(shí)間對于一個(gè)網(wǎng)站來說還是相當(dāng)重要的。如果是你的頭頭,給你一個(gè)url,讓你分析頁面為什么這么卡,你該如何辦呢?
查看頁面加載時(shí)間
現(xiàn)在的瀏覽器都是有開發(fā)者調(diào)試模式的,以chrome為例,打開頁面后,按ctrl+shift+i,或者點(diǎn)擊右鍵菜單里的檢查,就進(jìn)入了開發(fā)者模式,開發(fā)者模式里面有很多功能,跟頁面加載時(shí)間相關(guān)的就是network標(biāo)簽。如下圖所示:

在最下面的數(shù)據(jù)反應(yīng)了該頁面的整體加載情況。比如總共有111個(gè)請求,5.8M的數(shù)據(jù),以及三個(gè)時(shí)間點(diǎn),finish、DOMContentLoaded和load。
finish:頁面最后一個(gè)請求截止的時(shí)間,如果頁面加載完后,觸發(fā)了ajax請求,那么該時(shí)間會(huì)變更。
DOMContentLoaded:dom內(nèi)容加載并解析完成的時(shí)間
load:頁面所有的資源(圖片,音頻,視頻等)加載完成的時(shí)間
那什么是dom內(nèi)容加載完畢呢?我們從打開一個(gè)網(wǎng)頁說起。當(dāng)輸入一個(gè)URL,頁面的展示首先是空白的,然后過一會(huì),頁面會(huì)展示出內(nèi)容,但是頁面的有些資源比如說圖片資源還無法看到,此時(shí)頁面是可以正常的交互,過一段時(shí)間后,圖片才完成顯示在頁面。從頁面空白到展示出頁面內(nèi)容,會(huì)觸發(fā)DOMContentLoaded事件。而這段時(shí)間就是HTML文檔被加載和解析完成。
在這里我們可以明確DOMContentLoaded所計(jì)算的時(shí)間,當(dāng)文檔中沒有腳本時(shí),瀏覽器解析完文檔便能觸發(fā) DOMContentLoaded 事件;如果文檔中包含腳本,則腳本會(huì)阻塞文檔的解析,而腳本需要等位于腳本前面的css加載完才能執(zhí)行。在任何情況下,DOMContentLoaded 的觸發(fā)不需要等待圖片等其他資源加載完成。
接下來,我們來說說load,頁面上所有的資源(圖片,音頻,視頻等)被加載以后才會(huì)觸發(fā)load事件,簡單來說,頁面的load事件會(huì)在DOMContentLoaded被觸發(fā)之后才觸發(fā)。
通俗來講就是DOMContentLoaded是頁面白屏的時(shí)間,load是通常所說的頁面加載完成,瀏覽器不再轉(zhuǎn)菊花的時(shí)間。
影響頁面加載的因素
js性能太差,阻塞頁面
因?yàn)闉g覽器解析過程中,遇到<script>標(biāo)簽的時(shí)候,便會(huì)停止解析過程,轉(zhuǎn)而去處理腳本,如果腳本是內(nèi)聯(lián)的,瀏覽器會(huì)先去執(zhí)行這段內(nèi)聯(lián)的腳本,如果是外鏈的,那么先會(huì)去加載腳本,然后執(zhí)行。在處理完腳本之后,瀏覽器便繼續(xù)解析HTML文檔。
遇到j(luò)s阻塞的情況,就需要具體去看代碼的具體邏輯了,在這里不做詳述。
某個(gè)請求慢阻塞頁面的加載
一般遇到頁面卡頓,我們首先都是會(huì)想到去network里面去查看,可以一眼便能看出頁面具體的加載時(shí)間,以及每一個(gè)請求的詳細(xì)耗時(shí)。比如我們打開愛奇藝《2017你的追劇報(bào)告》這一活動(dòng)頁。

這一頁面其實(shí)在用戶看來應(yīng)該是5s左右就渲染完成,但是瀏覽器卻一直轉(zhuǎn)菊花,轉(zhuǎn)了足足1分鐘。最后顯示load的時(shí)間為1.2min,是為什么呢?

我們觀察network里的請求,status為200的毫無疑問是正常的,然后我們發(fā)現(xiàn)其中有一個(gè)一直pending狀態(tài)的,這個(gè)狀態(tài)的請求其實(shí)就是我們要找的罪魁禍?zhǔn)?,瀏覽器會(huì)一直等待這請求返回,直至超時(shí)或failed,才會(huì)觸發(fā)load事件。
同域名下的請求數(shù)過多導(dǎo)致Queueing
HTML頁面是如何完成
為了方便理解,我們首先需要知道一個(gè)頁面請求是如何完成的,即當(dāng)我們在瀏覽器輸入url按下enter后,究竟發(fā)生了一些什么事,這個(gè)應(yīng)該是個(gè)經(jīng)典面試題了,搜搜資料一大把,這里不詳細(xì)解釋。還是以愛奇藝那個(gè)活動(dòng)頁為例,url為http://www.iqiyi.com/common/2017summary.html
第一瀏覽器拿到你輸入的url,解析出域名www.iqiyi.com,得到域名之后肯定要與這個(gè)服務(wù)器建立連接,而TCP/IP協(xié)議中連接是需要知道IP地址,所以首先需要走DNS查詢,解析得到域名IP,然后再與該IP地址來三次經(jīng)典的握手建立TCP連接,如果協(xié)議頭是http則與目標(biāo)IP的80端口通信,如果是https則是443端口,建立好TCP連接后,就可以發(fā)送http請求了,告訴服務(wù)器我需要get /common/2017summary.html這個(gè)地址下的文件內(nèi)容,服務(wù)器收到請求,然后將這個(gè)2017summary.html超文本返回個(gè)瀏覽器,瀏覽器拿到這個(gè)html開始解析,blalalala......,最終就是我們所看到的頁面內(nèi)容。
頁面里單個(gè)請求的各個(gè)時(shí)間段的含義
一般情況下html頁面在解析渲染的過程中,會(huì)有大量的其他請求,比如外聯(lián)的css、js、圖片等等,這些資源都需要瀏覽器去重新發(fā)起http請求。這些請求其實(shí)也是類似的一個(gè)過程
我們再仔細(xì)看看這些單個(gè)請求的各個(gè)時(shí)間段,將鼠標(biāo)懸停在waterfall字段,就是那個(gè)五顏六色的??梢钥吹竭@個(gè)請求具體的耗時(shí),如下圖所示:

以下是各個(gè)時(shí)間段的含義:
Stalled/Blocking
Time the request spent waiting before it could be sent. This time is
inclusive of any time spent in proxy negotiation. Additionally, this
time will include when the browser is waiting for an already
established connection to become available for re-use, obeying
Chrome's maximum six TCP connection per origin rule.
Proxy Negotiation
Time spent negotiating with a proxy server connection.
DNS Lookup
Time spent performing the DNS lookup. Every new domain on a page
requires a full roundtrip to do the DNS lookup.
Initial Connection / Connecting
Time it took to establish a connection, including TCP
handshakes/retries and negotiating a SSL.
SSL
Time spent completing a SSL handshake.
Request Sent / Sending
Time spent issuing the network request. Typically a fraction of a
millisecond.
Waiting (TTFB)
Time spent waiting for the initial response, also known as the Time To
First Byte. This time captures the latency of a round trip to the
server in addition to the time spent waiting for the server to deliver
the response.
Content Download / Downloading
Time spent receiving the response data.
以下是翻譯的中文版,翻譯水平有限
Queueing請求排隊(duì)的時(shí)間。關(guān)于這個(gè),需要知道一個(gè)背景,就是瀏覽器與同一個(gè)域名建立的TCP連接數(shù)是有限制的,chrome設(shè)置的6個(gè),如果說同一時(shí)間,發(fā)起的同一域名的請求超過了6個(gè),這時(shí)候就需要排隊(duì)了,也就是這個(gè)Queueing時(shí)間
Stalled是瀏覽器得到要發(fā)出這個(gè)請求的指令,到請求可以發(fā)出的等待時(shí)間,一般是代理協(xié)商、以及等待可復(fù)用的TCP連接釋放的時(shí)間,不包括DNS查詢、建立TCP連接等時(shí)間等
DNS Lookup DNS查詢的時(shí)間,頁面內(nèi)任何新的域名都需要走一遍 完整的DNS查詢過程,已經(jīng)查詢過的則走緩存
Initial Connection / Connecting 建立TCP連接的時(shí)間,包括TCP的三次握手和SSL的認(rèn)證
SSL完成ssl認(rèn)證的時(shí)間
Request sent/sending請求第一個(gè)字節(jié)發(fā)出前到最后一個(gè)字節(jié)發(fā)出后的時(shí)間,也就是上傳時(shí)間
Waiting 請求發(fā)出后,到收到響應(yīng)的第一個(gè)字節(jié)所花費(fèi)的時(shí)間(Time To First Byte)
Content Download 收到響應(yīng)的第一個(gè)字節(jié),到接受完最后一個(gè)字節(jié)的時(shí)間,就是下載時(shí)間
這時(shí)候我們再回過頭來看愛奇藝活動(dòng)頁http://www.iqiyi.com/common/2017summary.html?uid=1417762088,我們剛剛已經(jīng)找到了一個(gè)阻塞頁面的請求,那個(gè)請求只是一個(gè)艾瑞統(tǒng)計(jì)的get請求,對頁面的業(yè)務(wù)邏輯無關(guān)緊要,暫時(shí)配置host讓這個(gè)請求直接404,這樣不會(huì)阻塞頁面加載。
配置host后,頁面加載的時(shí)間一般是3s,偶爾會(huì)超過5s情況,頁面先是變綠,然后變黑提示downloading,最后展示頁面。
這時(shí)候要再分析頁面的性能瓶頸就比較困難了。因?yàn)榇致話咭谎壅埱蟮臅r(shí)間,都是清一色幾十毫秒。這時(shí)候我們就需要分析那些最后完成的幾個(gè)請求耗時(shí)情況了,這可以在network里瀑布般的Timeline里很直觀的看出來

在timeline里,每一種顏色代表請求一個(gè)時(shí)間段,比如DNS查詢是深綠色,content download是藍(lán)色

我們再仔細(xì)觀察瀑布流的Timeline,里面每一條線代表就是一個(gè)TCP的連接,每條線的顏色一直循環(huán)變化,從灰色到深綠最后藍(lán)色,然后又變灰色,說明瀏覽器建立的TCP連接一直在循環(huán)復(fù)用,這樣就省去了三次握手的時(shí)間。瀑布流圖中,有一條藍(lán)色豎線和紅色豎線,藍(lán)色豎線表示觸發(fā)domContentLoad事件觸發(fā)時(shí)間,紅色表示Load事件觸發(fā)
最后我們發(fā)現(xiàn),耗時(shí)比較長的TCP連接有6個(gè),請求的都是圖片,而且是同一個(gè)域名,請求的數(shù)量達(dá)到100個(gè),所以從這里我們也可以看出,chrome對于同一域名的TCP連接最多建立6個(gè),如果請求過多,就會(huì)出現(xiàn)排隊(duì),也就是Queueing時(shí)間,我們挑幾個(gè)最后請求看看便知。


從上圖來看,最后幾個(gè)圖片的請求,排隊(duì)時(shí)間已經(jīng)接近1s了,而request sent、waiting、content download時(shí)間是非常少的。然后再仔細(xì)看,后面的圖片請求都沒有Initial Connection / Connecting時(shí)間,也就是沒有了TCP的建立過程

最后,我們大概知道這個(gè)頁面的性能瓶頸了,因?yàn)閳D片過多,然后點(diǎn)擊右鍵查看源代碼,看到開發(fā)定義了一個(gè)長長的數(shù)組,然后循環(huán)創(chuàng)建圖片,再給圖片的src賦值,這樣就會(huì)觸發(fā)瀏覽器發(fā)送圖片請求。
function loading() {
$('.m-loading').removeClass('hide');
var resCount = 0;
var length = images.length;
for (var i = 0; i < length; i++) {
var image = new Image();
image.src = images[i];
image.onload = onImageLoad;
image.onerror = onImageError;
}
那個(gè)黑色loading提示浮層,其實(shí)背后就是在預(yù)加載這些圖片,這樣之后在使用這些圖片的時(shí)候,就會(huì)稍微流暢一點(diǎn),因?yàn)槭∪チ苏埱蟮臅r(shí)間。但是這100來張圖片,其實(shí)并不是每次都需要加載,因?yàn)轫撁娣种鲬B(tài)和客態(tài),不同的視角用到的圖片不是一樣,所以大概20%的圖片其實(shí)是可以省去的,因?yàn)楦居貌簧希?dāng)然這就需要判斷,什么情況下需要加載哪些圖片。
利用chrome的performance開發(fā)工具

parse html或layout時(shí)間很長



從圖中看layout花了500+ms,這很可能是第一個(gè)請求比較慢導(dǎo)致。第一個(gè)請求總耗時(shí)500ms左右,主要消耗在content download,跟網(wǎng)速關(guān)系較大,還是html內(nèi)容太多,size那一列有兩行,上面的表示請求實(shí)際返回的數(shù)據(jù)大?。憫?yīng)頭+響應(yīng)內(nèi)容),大多數(shù)會(huì)采用采用gzip壓縮(只壓縮響應(yīng)內(nèi)容,不壓縮響應(yīng)頭),所以會(huì)比較小,下邊的是表示響應(yīng)內(nèi)容的大小(解壓后的)。那22kb下載了半秒,確實(shí)有點(diǎn)慢
參考
http://blog.csdn.net/itpinpai/article/details/52574385
http://blog.csdn.net/playboyanta123/article/details/46815441
https://segmentfault.com/q/1010000002399481/a-1020000002399855
https://www.cnblogs.com/caizhenbo/p/6679478.html
https://www.cnblogs.com/larennani/p/6741289.html
https://www.cnblogs.com/ranzige/p/4097453.html