2018你應該知道的Web性能信息採集指南

假設您正在訪問一個網站,若是Web內容不在幾秒內顯示在屏幕上,那麼做爲用戶您可能會選擇關閉標籤頁,轉去瀏覽其餘頁面從而代替這個網頁的內容。可是做爲Web開發者,您可能但願跟蹤請求導航的詳細信息,這樣你就能夠知道爲何這個網頁的速度在變慢。javascript

W3C性能工做組提供了能夠用來測量和改進Web應用性能的用戶代理(User Agent)特性與API。開發者可使用這些API來收集精確的性能信息,從不一樣方面找出Web應用的性能瓶頸並提升應用的性能。css

這些特性和API適用於桌面、移動瀏覽器以及其餘非瀏覽器環境。html

因爲這些特性與API不止適用於瀏覽器,還適用於非瀏覽器環境,因此本文會大量使用「用戶代理」這個詞來代替「瀏覽器」前端

1. 如何得到高精度的時間?

ECMA-262 規範中定義了 Date 對象來表示自 1970 年 1 月 1 日以來的毫秒數。它足以知足大部分需求,但缺點是時間會受到時鐘誤差與系統時鐘調整的影響。時間的值不老是單調遞增,後續值有可能會減小或者保持不變。java

例如,下面這段代碼計算出來的「duration」有可能被記錄爲正數、負數或零。git

const mark_start = Date.now()
doTask() // Some task
const duration = Date.now() - mark_start
複製代碼

上面這段代碼獲取的持續時間「duration」並不精準,它會受到時鐘誤差與系統時鐘調整的影響,因此最終獲得的「duration」可能爲正數、負數或零,咱們根本不知道它記錄的時間到底是不是正確的時間。github

高精度時間(High Resolution Time,簡稱hr-time)規範定義了Performance對象,經過Performance對象咱們能夠得到高精度的時間。canvas

Performance對象包含方法now和屬性timeOrigin跨域

  • 方法now被執行後會返回從 timeOrigin 到如今的高精度時間。數組

    當前時間 - performance.timeOrigin

  • 屬性timeOrigin返回頁面瀏覽上下文第一次被建立的時間。若是全局對象爲WorkerGlobalScope,那麼timeOrigin爲worker被建立的時間。

    timeOrigin 的時間值不受時鐘誤差與系統時鐘調整的影響。

    例如,當timeOrigin的值被肯定以後,不管將系統時間設置到什麼時間,下面代碼始終返回timeOrigin最初被賦予的時間:

    new Date(performance.timeOrigin).toLocaleString()
    // 2018/8/6 上午11:41:58
    複製代碼

若是兩個時間值擁有相同的時間起源(Time Origin),那麼使用 performance.now 方法返回的任意兩個按時間順序記錄的時間值之間的差值永遠不多是負數。

例如,下面這段代碼計算出來的「duration」永遠不可能爲負數。

const mark_start = performance.now()
doTask() // Some task
const duration = performance.now() - mark_start
複製代碼

經過performance.timeOrigin + performance.now 能夠獲得精準的當前時間。該時間不受時鐘誤差與系統時鐘調整的影響。

不受時鐘誤差與系統時鐘調整的影響指的是當timeOrigin的值被肯定以後修改了系統時間,這時候timeOrigin不會受到影響。

const timeStamp = performance.timeOrigin + performance.now()
console.log(timeStamp) // 1533539552977.5718
new Date(timeStamp).toLocaleString()
// "2018/8/6 下午3:10:42"
複製代碼

2. 性能時間線(Performance Timeline)

在介紹如何獲取性能指標以前,咱們須要先介紹「性能時間線」,它提供了統一的接口來獲取各類性能相關的度量數據。它是本文即將要介紹的其餘獲取性能指標方法的基礎。

「性能時間線」自己並不提供任何性能信息,但它會提供一些方法,當您想要得到性能信息時,可使用「性能時間線」提供的方法來獲得您想獲取的性能信息。

本文後面會詳細介紹從「性能時間線」中能夠訪問哪些性能信息

2.1 擴展Performance對象

「性能時間線」擴展了Performance對象,新增了一些用於從「性能時間線」中獲取性能指標數據的屬性與方法。

下表給出了在Performance對象上新增的方法:

方法名 做用
getEntries() 返回一個列表,該列表包含一些用於承載各類性能數據的對象,不作任何過濾
getEntriesByType() 返回一個列表,該列表包含一些用於承載各類性能數據的對象,按類型過濾
getEntriesByName() 返回一個列表,,該列表包含一些用於承載各類性能數據的對象,按名稱過濾

表中給出了三個方法,使用這些方法能夠獲得一個列表,列表中包含一系列用於承載各類性能數據的對象。換句話說,使用這些對象能夠獲得咱們想要得到的各類性能信息。

在術語上這個列表叫作PerformanceEntryList,而列表中的對象叫作PerformanceEntry

不一樣方法的過濾條件不一樣,因此列表中的PerformanceEntry對象所包含的數據也不一樣。

2.2 PerformanceEntry對象

「性能時間線」定義了PerformanceEntry對象,該對象承載了各類性能相關的數據。下表給出了PerformanceEntry對象所包含的屬性:

屬性名 做用
name 經過該屬性能夠獲得PerformanceEntry對象的標識符,不惟一
entryType 經過該屬性能夠獲得PerformanceEntry對象的類型
startTime 經過該屬性能夠獲得一個時間戳
duration 經過該屬性能夠獲得持續時間

從上表中能夠發現,「性能時間線」並無明肯定義PerformanceEntry對象應該返回什麼具體內容,它只是定義了一個格式,返回的具體內容會根據咱們獲取的性能數據類型的不一樣而不一樣。本文的後面咱們會詳細介紹。

2.3 PerformanceObserver

「性能時間線」還定義了一個很是重要的接口用來觀察「性能時間線」記錄新的性能信息,當一個新的性能信息被記錄時,觀察者將會收到通知。它就是PerformanceObserver。例如,能夠經過下面代碼定義一個長任務觀察者:

const observer = new PerformanceObserver(function (list) {
  // 當記錄一個新的性能指標時執行
})
// 註冊長任務觀察者
observer.observe({entryTypes: ['longtask']})
複製代碼

上面這段代碼使用PerformanceObserver註冊了一個長任務觀察者,當一個新的長任務性能信息被記錄時,回調會被觸發。

回調函數會接收到兩個參數:第一個參數是一個列表,第二個參數是觀察者實例。

在術語上這個列表被稱爲PerformanceObserverEntryList,而且包含三個方法getEntriesgetEntriesByTypegetEntriesByName。能夠經過這三個方法得到PerformanceEntryList列表。這三個方法功能於使用方式均與前面介紹的相同。

3. 如何收集「資源加載」相關性能度量數據?

獲取資源加載相關的時間信息可讓咱們知道咱們的頁面須要讓用戶等待多久。下面這段簡單的JavaScript代碼嘗試測量加載資源所需的時間:

<!doctype html>
<html>
  <head></head>
  <body onload="loadResources()">
    <script> function loadResources() { const start = new Date().getTime() const image1 = new Image() const resourceTiming = function() { const now = new Date().getTime() const latency = now - start console.log('End to end resource fetch: ' + latency) } image1.onload = resourceTiming image1.src = 'https://www.w3.org/Icons/w3c_main.png' } </script>
    <img src="https://www.w3.org/Icons/w3c_home.png">
  </body>
</html>
複製代碼

雖然這段代碼能夠測量資源的加載時間,但它不能得到資源加載過程當中各個階段詳細的時間信息。同時這段代碼並不能投放到生產環境,由於它有不少問題:

  • 在CSS中使用@import url()background: url()加載的資源應該如何測量計時信息?
  • 如何測量經過HTML標籤元素加載的資源的計時信息?例如linkimgscript
  • 若是資源是經過xmlhttprequest請求的,如何測量資源的計時信息?
  • 經過fetch方法請求的資源如何測量計時信息?
  • 經過beacon發送的請求如何測量計時信息?
  • 上面代碼並不通用,如何測量全部資源的加載信息?
  • 還有不少其餘狀況都沒法測量

幸運的是,W3C性能工做組定義了資源計時(Resource Timing)規範讓Web開發者能夠獲取很是詳細的資源計時信息。

下面這個例子能夠獲取更加詳細的資源計時信息:

<!doctype html>
<html>
  <head>
  </head>
  <body onload="loadResources()">
    <script> function loadResources () { const image1 = new Image() image1.onload = resourceTiming image1.src = 'https://www.w3.org/Icons/w3c_main.png' } function resourceTiming () { const resourceList = window.performance.getEntriesByType('resource') for (let i = 0; i < resourceList.length; i++) { console.log('End to end resource fetch: ' + (resourceList[i].responseEnd - resourceList[i].startTime)) } } </script>
    <img id="image0" src="https://www.w3.org/Icons/w3c_home.png">
  </body>
</html>
複製代碼

上面代碼經過performance.getEntriesByType方法獲得一個列表,這個列表就是咱們前面介紹的PerformanceEntryList,並過濾出全部類型爲resourcePerformanceEntry對象。

類型爲resourcePerformanceEntry對象在術語上被稱爲PerformanceResourceTiming對象。

PerformanceResourceTiming對象擴展了PerformanceEntry對象並新增了不少屬性用於獲取詳細的資源計時信息,PerformanceResourceTiming對象的全部屬性與其對應的做用以下表所示:

屬性名 做用
name 請求資源的絕對地址,即使請求重定向到一個新的地址此屬性也不會改變
entryType PerformanceResourceTiming對象的entryType屬性永遠返回字符串「resource」
startTime 用戶代理開始排隊獲取資源的時間。若是HTTP重定則該屬性與redirectStart屬性相同,其餘狀況該屬性將與fetchStart相同
duration 該屬性將返回 responseEndstartTime之間的時間
initiatorType 發起資源的類型
nextHopProtocol 請求資源的網絡協議
workerStart 若是當前上下文是」worker」,則workerStart屬性返回開始獲取資源的時間,不然返回0
redirectStart 資源開始重定向的時間,若是沒有重定向則返回0
redirectEnd 資源重定向結束的時間,若是沒有重定向則返回0
fetchStart 開始獲取資源的時間,若是資源重定向了,那麼時間爲最後一個重定向資源的開始獲取時間
domainLookupStart 資源開始進行DNS查詢的時間(若是沒有進行DNS查詢,例如使用了緩存或本地資源則時間等於fetchStart)
domainLookupEnd 資源完成DNS查詢的時間(若是沒有進行DNS查詢,例如使用了緩存或本地資源則時間等於fetchStart)
connectStart 用戶代理開始與服務器創建用來檢索資源的鏈接的時間(TCP創建鏈接的時間)
connectEnd 用戶代理完成與服務器創建的用來檢索資源的鏈接的時間(TCP鏈接成功的時間)
secureConnectionStart 如資源使用安全傳輸,那麼用戶代理會啓動握手過程以確保當前鏈接。該屬性表明握手開始時間(若是頁面使用HTTPS那麼值是安全鏈接握手以前的時間)
requestStart 開始請求資源的時間
responseStart 用戶代理開始接收Response信息的時間(開始接受Response的第一個字節,例如HTTP/2的幀頭或HTTP/1.x的Response狀態行)
responseEnd 用戶代理接收到資源的最後一個字節的時間,或在傳輸鏈接關閉以前的時間,使用先到者的時間。或者是因爲網絡錯誤而終止網絡的時間
transferSize 表示資源的大小(以八位字節爲單位),該大小包括響應頭字段和響應有效內容主體(Payload Body)
encodedBodySize 表示從HTTP網絡或緩存中接收到的有效內容主體(Payload Body)的大小(在刪除全部應用內容編碼以前)
decodedBodySize 表示從HTTP網絡或緩存中接收到的消息主體(Message Body)的大小(在刪除全部應用內容編碼以後)

因爲有一些屬性功能比較複雜,下面將針對一些功能比較複雜的屬性詳細介紹。

3.1 initiatorType

簡單來講initiatorType屬性返回的內容表明資源是從哪裏發生的請求行爲。

initiatorType屬性會返回下面列表中列出的字符串中的其中一個:

類型 描述
css 若是請求是從CSS中的url()指令發出的,例如 @import url()background: url()
xmlhttprequest 經過XMLHttpRequest對象發出的請求
fetch 經過Fetch方法發出的請求
beacon 經過beacon方法發出的請求
link 經過link標籤發出的請求
script 經過script標籤發出的請求
iframe 經過iframe標籤發出的請求
other 沒有匹配上面條件的請求

3.2 domainLookupStart

準確的說,domainLookupStart屬性會返回下列值中的其中一個:

  • 若是使用了持久鏈接(persistent connection),或者從相關應用緩存(relevant application cache)或從本地資源中獲取資源,那麼domainLookupStart的值與fetchStart相同
  • 若是用戶代理在緩存中具備域信息,那麼domainLookupStart等於開始從域信息緩存中檢索域數據的時間
  • 用戶代理開始對資源進行域名查詢前的時間
  • 其餘狀況爲0

3.3 domainLookupEnd

domainLookupEnd屬性會返回下列值中的其中一個:

  • domainLookupStart相同,若是使用了持久鏈接(persistent connection),或者從相關應用緩存(relevant application cache)或本地資源中獲取資源,那麼domainLookupEnd的值與fetchStart相同
  • 若是用戶代理在緩存中具備域信息,那麼domainLookupEnd爲從域信息緩存中檢索域數據結束時的時間
  • 用戶代理完成對資源進行域名查詢的時間
  • 其餘狀況爲0

3.4 過程模型

下圖給出了PerformanceResourceTiming對象定義的時序屬性。當從不一樣來源獲取資源時,括號中的屬性可能不可用。用戶代理能夠在時間點之間執行內部處理。

圖1 PerformanceResourceTiming接口定義的時序屬性

圖1 PerformanceResourceTiming 過程模型

4. 如何收集「網頁加載」相關性能度量數據?

精準地測量Web應用的性能是使Web應用更快的一個重要方面。雖然利用JavaScript提供的能力能夠測量用戶等待時間(咱們常說的埋點),但在更多狀況下,它並不能提供完整或詳細的等待時間。例如,下面的JavaScript使用了一個很是天真的方式嘗試測量頁面徹底加載完所須要的時間:

<html>
  <head>
    <script type="text/javascript"> const start = new Date().getTime() function onLoad() { const now = new Date().getTime() const latency = now - start console.log('page loading time: ' + latency) } </script>
  </head>
  <body onload="onLoad()">
    <!- Main page body goes from here. -->
  </body>
</html>
複製代碼

上面的代碼將計算在執行head標籤中的第一行JavaScript以後加載頁面所需的時間,可是它沒有提供任何有關從服務端獲取頁面所需的時間信息,或頁面的初始化生命週期。

對於這種需求,W3C性能工做組定義了Navigation Timing規範,該規範定義了PerformanceNavigationTiming接口,提供了更有用和更準確的頁面加載相關的時間數據。包括從網絡獲取文檔到在用戶代理(User Agent)中加載文檔相關的全部時間信息。

對於上面那個例子,使用Navigation Timing能夠很輕鬆的用下面的代碼作到而且更精準:

<html>
  <head>
    <script type="text/javascript"> function onLoad() { const [entry] = performance.getEntriesByType('navigation') console.log('page loading time: ' + entry.duration) } </script>
  </head>
  <body onload="onLoad()">
    <!- Main page body goes from here. -->
  </body>
</html>
複製代碼

上面代碼經過performance.getEntriesByType方法獲得一個列表,這個列表就是咱們前面2.1節介紹的PerformanceEntryList,並過濾出全部類型爲navigationPerformanceEntry對象。

類型爲navigationPerformanceEntry對象在術語上被稱爲PerformanceNavigationTiming對象。

PerformanceNavigationTiming對象擴展了PerformanceEntry對象,經過該對象提供的duration屬性能夠獲得頁面加載所消耗的所有時間。

PerformanceNavigationTiming 接口所提供的全部時間值都是相對於 Time Origin 的。因此 startTime 屬性的值永遠是0

經過該PerformanceNavigationTiming對象能夠得到頁面加載相關的很是精準的時間信息:

  • name:當前頁面的地址
  • entryType:「navigation」
  • startTime:0
  • duration:頁面加載所消耗的所有時間(loadEventEnd的時間減去startTime的時間)

PerformanceNavigationTiming對象擴展了PerformanceResourceTiming對象,因此PerformanceNavigationTiming對象具備PerformanceResourceTiming對象的全部屬性,可是某些屬性的返回值略有不一樣:

  • initiatorType:「navigation」
  • workerStart:頁面開始註冊Service Worker的時間

同時 NavigationTiming 新增了一些屬性,下面列表給出了新增的屬性:

新增的屬性 描述
unloadEventStart 若是被請求的頁面來自於前一個同源(同源策略)的文檔,那麼該屬性存儲的值是瀏覽器開始卸載前一個文檔的時刻。不然的話(前一個文檔非同源或者沒有前一個文檔)爲0
unloadEventEnd 前一個文檔卸載完成的時刻。若是前一個文檔不存在則爲0
domInteractive 指文檔完成解析的時間,包括在「傳統模式」下被阻塞的經過script標籤加載的內容(使用defer或者async屬性異步加載的狀況除外)
domContentLoadedEventStart DOMContentLoaded事件觸發前的時間
domContentLoadedEventEnd DOMContentLoaded事件觸發後的時間
domComplete 用戶代理將將document.readyState設置爲complete的時間
loadEventStart load事件被觸發前的時間,若是load事件還沒觸發則返回0
loadEventEnd load事件完成後的時間,若是load事件還沒觸發則返回0
redirectCount 頁面被重定向的次數
type 頁面被載入的方式

type屬性的四種取值狀況:

  1. navigate:用戶經過點擊連接或者在瀏覽器地址欄輸入URL的方式進入頁面
  2. reload:經過從新加載操做或location.reload()方法
  3. back_forward:經過瀏覽器history的前進或後退進入頁面
  4. prerender:經過prerender的方式啓動一個頁面

4.1 過程模型

圖2給出了PerformanceNavigationTiming對象的時序屬性。當頁面從不一樣來源獲取時,括號中的屬性可能不可用。

圖2 PerformanceNavigationTiming 過程模型

圖2 PerformanceNavigationTiming 過程模型

從圖2能夠看出完整的頁面加載時間信息包含不少信息。前端渲染相關的時間只佔用不多的一部分(圖2最後面兩個藍色部分processingonLoad)。這也是爲何咱們在一開始說使用JS埋點的方式去測量頁面加載時間很天真。

5. 使用高精度時間戳來度量Web應用的性能

Web開發者須要一種可以**「評估與理解」**其Web應用性能的能力。雖然JavaScript提供了測量應用性能的能力(使用Date.now()方法獲取當前時間戳),但這個時間戳的精度在不一樣的用戶代理下存在必定的差別,而且時間會受到系統時鐘誤差與調整的影響。

W3C性能工做組定義了User Timing規範,提供了高精度且單調遞增的時間戳,使開發者能夠更好地測量其應用的性能。

下面代碼顯示了開發者應該如何使用User Timing規範定義的API來得到執行代碼相關的時間信息。

async function run() {
  performance.mark("startTask1")
  await doTask1() // Some developer code
  performance.mark("endTask1")

  performance.mark("startTask2")
  await doTask2() // Some developer code
  performance.mark("endTask2")

  // Log them out
  const entries = performance.getEntriesByType("mark")
  for (const entry of entries) {
    console.table(entry.toJSON())
  }
}
run()
複製代碼

5.1 關於User Timing

User Timing規範擴展了Performance對象,並在Performance對象上新增了四個方法:

  • mark
  • clearMarks
  • measure
  • clearMeasures

5.1.1 mark方法

mark方法接收一個字符串類型的參數(mark名稱),用於建立並存儲一個PerformanceMark對象。更通俗的說,mark方法用於記錄一個與名稱相關時間戳。

PerformanceMark對象存儲了4個屬性:

  • name:mark方法的參數
  • entryType:「mark」
  • startTime:mark方法被調用的時間(performance.now()方法的返回值)
  • duration:0

下面代碼展現瞭如何使用mark方法:

performance.mark('testName')
複製代碼

當使用mark方法存儲了一個PerformanceMark對象後,能夠經過前面介紹的getEntriesByName方法獲得一個列表,列表中包含一個PerformanceMark對象。代碼以下:

const [entry] = performance.getEntriesByName('testName')
console.log(entry) // {"name": "testName", "entryType": "mark", "startTime": 4396.399999997811, "duration": 0}
複製代碼

5.1.2 clearMarks方法

顧名思義,clearMarks方法的做用是刪除全部給定名稱的時間戳數據(PerformanceMark對象)。

clearMarks方法接收一個字符串類型的參數(mark名稱),例如:

performance.mark('testName')
performance.clearMarks('testName')
performance.getEntriesByName('testName') // []
複製代碼

上面代碼使用mark方法記錄了一個名爲testName的時間戳信息(存儲了PerformanceMark對象),隨後使用clearMarks方法清除名爲testName的時間戳信息,最後嘗試獲取名爲testName的時間戳信息時獲得的是一個空列表。

5.1.3 measure方法

雖然mark方法能夠記錄時間戳信息,可是得到兩個mark之間的持續時間仍是有點麻煩,咱們須要先獲取兩個PerformanceMark對象,而後再執行減法。

針對這個問題User Timing規範提供了measure方法,該方法的做用是使用一個名字將兩個PerformanceMark對象之間所持續的時間存儲起來。

measure方法的參數:

  1. measureName:名稱
  2. startMark:mark名稱
  3. endMark:mark名稱

mark方法相同,measure方法會建立一個PerformanceMeasure對象並存儲起來。PerformanceMeasure對象存儲了4個屬性:

  • name:參數中提供的measureName
  • entryType:「measure」
  • startTime:PerformanceMark對象的startTime屬性,若是沒有提供startMark參數,則爲0
  • duration:兩個PerformanceMark對象的startTime屬性的差值,多是負數。

下面代碼展現瞭如何使用measure方法檢測代碼執行所持續的時間:

async function run() {
  performance.mark('startTask')
  await doTask1() // Some developer code
  performance.mark('endTask')

  performance.measure('task', 'startTask', 'endTask')
  // Log them out
  const [entry] = performance.getEntriesByName('task')
  console.log(entry.duration)
}
run()
複製代碼

5.1.4 clearMeasures方法

clearMarks相似,clearMeasures方法的做用是使用參數中提供的名稱來刪除PerformanceMeasure對象。

6. 如何知道「用戶以爲網頁慢」(如何檢測長任務)?

保證UI的流暢很重要,那麼如何檢測UI是否流暢呢?

根據RAIL性能模型提供的信息,若是Web應用在100毫秒內的時間能夠響應用戶輸入,則用戶會以爲應用的交互很流暢。若是響應超過100毫秒用戶就會感受到應用有點輕微的延遲。若是超過1秒,用戶的注意力將離開他們正在執行的任務。

因爲JavaScript是單線程的,因此當一個任務執行時間過長,就會阻塞UI線程與其餘任務。對於用戶來講,他一般會看到一個「鎖定」的頁面,瀏覽器沒法響應用戶輸入。

這種佔用UI線程很長一段時間並阻止其餘關鍵任務執行的任務叫作「長任務」

更具體的解釋是:超過50毫秒的事件循環任務都屬於長任務

那麼如何檢測應用是否存在「長任務」呢?

一個已知的方式是使用一個短週期定時器,並檢查兩次調用之間的時間,若是兩次調用之間的時間大於定時器的週期時間,那麼頗有可能有一個或多個「長任務」延遲了定時器的執行。

這種方式雖然能夠實現需求,但它並不完美。它要不停的輪詢去檢查長任務,在移動端對手機電池壽命不友好,而且也沒有辦法知道是誰形成了延遲(例如:本身的代碼 vs 第三方的代碼)。

W3C性能工做組提供了Long Tasks規範,該規範定義了一個接口,使Web開發者能夠監測「長任務」是否存在。

使用案例:

const observer = new PerformanceObserver(function(list) {
  const perfEntries = list.getEntries()
  for (let i = 0; i < perfEntries.length; i++) {
    // 處理長任務通知
    // 上報性能檢測數據
    // ...
  }
})
// 註冊長任務觀察者
observer.observe({entryTypes: ['longtask']})

// 模擬一個長任務
const start = Date.now()
while (Date.now() - start < 1000) {}
複製代碼

上面的代碼註冊了「長任務」觀察器,它的功能是每當有超過50毫秒的任務被執行時調用回調函數。

2.3節介紹了PerformanceObserver,因此回調函數中的變量perfEntries保存了一個列表,列表中包含了全部承載了長任務數據的對象。

承載了長任務數據的對象在術語上被稱爲PerformanceLongTaskTiming

PerformanceLongTaskTiming對象中保存了長任務相關的信息,包括如下屬性:

  • name:name屬性提供了長任務的來源信息,一般返回「self」但也有一些其餘來源信息
    • self:長任務來自自身frame
    • same-origin-ancestor:長任務來自一個同源的祖先frame(註冊長任務觀察者的頁面被iframe到一個同源的其餘頁面時,咱們叫這個其餘頁面爲父級頁面,若是這個父級頁面出現了長任務,那麼在子頁面中的長任務觀察者會獲得通知,這時候name屬性的值爲same-origin-ancestor
    • same-origin-descendant:長任務來自一個同源的後代frame(與same-origin-ancestor相反,若是當前頁面註冊了一個長任務觀察者並iframe了一個其餘頁面,這時候iframe中若是存在長任務,則當前頁面的長任務觀察者會收到通知,這時候name屬性的值爲same-origin-descendant
    • same-origin:長任務來自一個同源但沒法訪問的frame
    • cross-origin-ancestor:長任務來自跨域的祖先frame
    • cross-origin-descendant:長任務來自跨域的後代frame
    • cross-origin-unreachable:長任務來自跨域但沒法訪問的frame
    • multiple-contexts:長任務涉及多個frame(據我我測試,在當前頁面註冊的長任務觀察者,而且在當前頁面觸發的長任務,可是這個頁面是被iframe到另外一個頁面,而後父級頁面的console中查看子頁面打印的PerformanceLongTaskTiming對象的name屬性爲multiple-contexts
    • unknown:上面這些狀況都不符合
  • entryType:屬性必須返回「longtask」
  • startTime:長任務開始的時間,該時間是相對於Time Origin的時間
  • duration:長任務的持續時間
  • attribution:一個數組,但長度永遠等於1,裏面是一個TaskAttributionTiming對象,該對象有如下屬性:
    • name:如今name屬性老是返回「script」,但將來name屬性將用於標識佈局,繪製等信息
    • entryType:老是返回「taskattribution」
    • startTime:老是返回0
    • duration:老是返回0
    • containerType:瀏覽上下文容器類型,例如:「iframe」,「embed」,「object」
    • containerName:瀏覽上下文容器名稱
    • containerId:瀏覽上下文容器id
    • containerSrc:瀏覽上下文容器src

frame指的是瀏覽上下文,例如iframe

7. 如何收集「首屏渲染」相關性能度量數據?

加載並非一個單一的時刻,它是一種體驗,沒有任何一種指標能夠徹底捕獲。事實上在頁面加載期間有多個時刻能夠影響用戶將其視爲「快」仍是「慢」。

首次繪製(FP,全稱First Paint)是第一個比較關鍵的時刻,其次是首次內容繪製(FCP,全稱First Contentful Paint)。

這兩個性能指標之間的主要區別在於「首次繪製」是當瀏覽器首次開始渲染任何能夠在視覺上讓屏幕發生變化的時刻。相比之下「首次內容繪製」是當瀏覽器首次從DOM中渲染內容的時刻,內容能夠是文本,圖片,SVG,甚至是canvas元素。

速度度量
圖3 首屏渲染指標

」首次繪製「(First Paint)不包括默認背景繪製(例如瀏覽器默認的白色背景),可是包含非默認的背景繪製,與iframe。

」首次內容繪製「(First Contentful Paint)包含文本,圖片(包含背景圖),非白色canvas與SVG。

父級瀏覽上下文不該該知道子瀏覽上下文的繪製事件,反之亦然。這就意味着若是一個瀏覽上下文只包含一個iframe,那麼將只有「首次繪製」,但沒有「首次內容繪製」。

能夠經過下面代碼得到首屏渲染性能指標數據:

performance.getEntriesByType('paint')
複製代碼

經過上面這行代碼能夠獲得一個列表。列表中包含一個或兩個PerformancePaintTiming對象。這取決於「首次內容繪製」是否存在。如圖4所示:

獲取首屏渲染指標

圖4. 獲取首屏渲染指標

從圖3能夠看到PerformancePaintTiming對象包含四個屬性,這四個屬性的值爲:

  • name:若是是首次繪製則name爲「first-paint」,若是是「首次內容繪製」則name爲「first-contentful-paint」
  • entryType:「paint」
  • startTime:繪製發生的時間,該時間是相對於time origin
  • duration:0

咱們可使用下面的代碼註冊一個繪製觀察器:

const observer = new PerformanceObserver(function(list) {
    const perfEntries = list.getEntries()
    for (let i = 0; i < perfEntries.length; i++) {
        // 處理數據
        // 上報性能檢測數據
        // ...
    }
})

// 註冊繪製觀察者
observer.observe({entryTypes: ["paint"]})
複製代碼

8. 總結

本文詳細介紹了在Web應用中採集性能信息所須要的一些方法。其中包括:得到不受時鐘誤差與系統時鐘調整影響的高精度時間的方法、收集「頁面資源加載」相關的性能度量數據的方法、收集「網頁加載」相關的性能度量數據的方法、使用高精度時間戳在應用程序中埋點的方法、監測用戶以爲網頁「慢」的方法以及採集首屏渲染性能指標的方法。

相關文章
相關標籤/搜索