【理解chrome開發者工具】 part2 網絡,性能與內存

網絡 Network

瀑布流

隨着網頁的加載,每個HTTP請求都會是瀑布流中的一條。第一條都是文件document的加載,當文件被解析,隨後通常是CSS文件的加載。和寫在HTML文件中的標籤中的順序是一樣的。但瀏覽器會做一些優化,比如會降低圖片的優先度,提升CSS文件的優先度等。

在瀑布流下方的表格中,我們可以看到請求的Name,Status,Type等信息。Initiator列的意思是,什麼文件需求加載了這一行的文件。按住Shift點擊表格的一行,調用該行文件的相應文件會變綠(who called it?),該行文件調用的相應行會變紅(who does it called?)。

瀑布流顏色

  • 白色 隊列中。不常見。HTTP1中,瀏覽器一次性只能進行6個TCP連接,比如我們有7個CSS文件,那麼其中的一個就會在隊列中。
  • 灰色 一個請求能發送前的各種停滯反應時間。
  • 淺灰色 在Proxy代理服務器消耗的時間
  • 深綠色 DNS查找的時間
  • 橙色 建立連接時間。包括TCP握手時間和建立SSL連接時間。
  • 棕色 SSL連接時間
  • 綠色 等待回覆的時間。也就是等待獲得第一個字節的時間。如果綠色很長說明應用服務器很慢。
  • 藍色 下載回覆內容的時間。和文件大小有關係。

快照

如果我們點擊Capture Screenshot按鈕,重新加載頁面, 就可以看到網頁的每次repaint,也就是網頁是如何加載的。

使用這個功能,我們可以知道在慢速網絡下,網頁是如何呈現出來的。

過濾信息

Network面板中,我們可以點擊文件類型來查看特定類型的文件。左側有個輸入框也可以輸入特定條件。比如larger-than:200px,就可以觀察大於200px的圖片請求。

Disable Cache,Offline,Preserve Log三個按鈕的功能是顯而易見的。

性能 Performance

開發者性能VS用戶端性能測試

開發者性能測試是在開發環境中做性能測試,但是用戶端是在真實用戶使用的情況下記錄測試數據。以前是這麼做的:

const start = new Date().getTime();

const end = new Date().getTime();

const time = end - start;

這樣我們就可以記錄下用戶作出一個操作的時間,然後將數據post回來。

後來有了Performance API:

performance.mark('start')

performance.mark('end);

performance.measure('Our Measurement,'start','end');

performance.getEntriesByType('measure')

圖片性能

一般圖片過大的解決方法:

  • resize圖片
  • 刪除圖片的meta data
  • 在服務器端使用gzip,brotli, zopfli等工具。

還有一個HTML API要知道,srcset
可以在不同窗口大小的時候加載指定的圖片。

<img srcset="small.jpg  300w,
              medium.jpg 800w,
              large.jpg 1200w">

但是爲了瀏覽器兼容性,我們總是應該給默認的src attribute留一個URL。

頁面卡頓 Page Jank

爲什麼看起來卡

現代大多數屏幕刷新率都是60幀每秒。1秒/60 = 16.66毫秒。所以我們的一個畫面更新的處理時間如果超過了16毫秒,就感覺卡。

編譯時間慢

有一種卡的原因是因爲,解析Javascript的時間過長。V8引擎渲染頁面的時候,需要編譯Javascript,生成AST(Abstract Syntax Tree)。當客戶端的處理性能很差的時候,就要等很久。

佈局抖動

還有一種卡的原因是因爲,Layout thrashing,反覆佈局,又稱佈局抖動。

//Read
const h1 = element.clientHeight;
//Write
element1.style.height = (h1 * 2) + 'px';
//Read
const h2 = element2.clientHeight;
//Write
element2.sytle.height = (h2 * 2) + 'px';

當我們反覆進行這種DOM讀寫操作的時候,就會造成佈局抖動。

requestAnimationFrame

如果我們可以將讀和寫完全分開,一次性操作所有讀,一次性操作所有寫,就不會有這種情況,但是這是不現實的。這就是使用window.requestAnimationFrame()的原因。

簡單說requestAnimationFrame將讀寫操作和屏幕刷新率匹配起來,當瀏覽器準備好更新下一幀時,做想做的操作,減少性能浪費,避免跳幀。關於這個話題有很多寫Event Loop的文章裏也有寫,這裏不多寫。

觀察頁面重繪情況

在開發者工具中,我們在更多工具中可以找到Rendering的選項。

圖片描述

打開這個選項,頁面每次repaint的地方都會變成綠色,幫助開發者觀察是否一些沒必要重繪的地方在不停的被重繪,浪費性能。

錄製操作和分析

我們點擊錄製按鈕,在頁面上做一些操作,比如Scroll。Performance面板中就會有一些數據。
圖片描述

最下方有一個餅狀圖是一個概括總結。

展開Main行,X軸代表處理時間,Y軸是Call Stack。Y軸高沒關係,只是函數之間不斷的調用,但是如果有色塊很寬的話就說明處理時間很長。

我們可以使用WASD來操作。按W Zoom In可以看到具體信息。

找到一個很寬的色塊,看之前是哪個色塊調用了這個色塊,然後我們就可以在下方Summary面板中找到具體文件名,點擊去Sources面板看代碼。

內存 Memory

JS中的內存泄漏

當一些內存沒有按開發者的意願被釋放的時候,就出現了內存泄漏。

常見的內存泄漏情況

  • 意外添加的全局變量
function foo() {
  bar = "Hi"
}

當foo被調用的時候,因爲bar沒有變量聲明關鍵詞const, var, let。JS就會一直向上找這個變量到全局作用域,然後會爲你創建一個全局變量bar。當這個函數結束的時候,你以爲這個bar會被回收,但其實它一直留在全局。設想如果這個bar不是“Hi”而是一個擁有很多元素的array,它留在了全局作用域,這並不是我們想要的情況。

  • 沒有取消的計時器

顧名思義,計時器沒有被取消或移除。

  • 多餘的DOM元素變量
const button = document.getElementById('button);

document.body.removeChild(
  document.getElementById('button)
);
}

這裏我們在DOM中移除了這個button,但是之前指向這個元素的reference還在,就是變量button。所以這個reference就留在了內存中。

發現內存泄漏

打開Chrome的任務管理器,確保Javascript Memory列有顯示。我們可以看到每個標籤頁的內存使用情況。如果有一個標籤頁的內存使用不穩定,一直在上升,說明出現了內存泄漏。

在Performance面板記錄下的數據中,如果我們打勾Memory選項,我們就可以看到Memory行。如果線圖不停的上升,就說明出現了內存泄漏。

定位內存泄漏

在開發者工具的Memory面板中,我們可以選擇Heap Snapshot,記錄一個當前頁面具體內存使用情況的快照。注意Shallow Size列和Retained Size列。

Shallow Size是對象自身佔用內存的大小,而Retained Size是指我們移除Object後能獲得多少空間,也就是將對象本身和連同的相關對象一起刪除後釋放的內存大小。比如一個變量指向一個很大的Object,這個變量本身是個reference很小(Shallow Size很小),但是移除這個變量以後,我們就可以獲得很大的空間(Retained Size很大)。

我們可以根據Shallow Size給Heap Snapshot排序,找到佔用內存最多的對象,如果不確認是否是內存泄漏,可以再記錄一個Heap Snapshot做對比。如果該對象的Shallow Size增長了,說明確實出現了內存泄漏。我們可以根據工具給的提示信息,找到開發代碼片段做修改。

評估 Audit

現在的Audit面板整合了谷歌的Lighthouse服務。網上還有一些其他不錯的第三方服務如webpagetest, sonarwhal。

官方文檔
Chrome Developer Tool

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章