本教程解釋瞭如何使用Performance API來記錄真實使用者訪問你的應用程式的統計資料。
使用瀏覽器的DevTools來評估web應用效能是很有用的,但要復現現實世界的使用情況並不容易。因為人們在不同地點使用不同的裝置、瀏覽器和網路,都會有不同的體驗。
Performance API介紹
Performance API使用一個緩衝區,在你的網頁生命週期的確定節點上,在物件屬性中記錄類似DevTool的指標。這些節點包括:
- 頁面導航:記錄頁面載入重定向、連線、握手、DOM事件等等。
- 資源載入:記錄資源載入,比如影像、CSS、指令碼以及Ajax呼叫。
- 繪製指標:記錄瀏覽器渲染資訊。
- 自定義:記錄任意的應用處理時間,來找到執行慢的函式。
所有的API都可以在客戶端的JavaScript中使用,包括Web Workers。你可以用以下方法檢測API支援情況:
if ('performance' in window) {
// call Performance APIs
}
注意:儘管Safari實現了大部分的API,但Safari並不支援所有的方法。
自定義performance API也被複制到了:
- Node.js 內建
performance_hook
模組,以及 - Deno performance API,(使用它的指令碼必須以
--allow-hrtime
許可權執行)。
Date()
不夠好嗎
你可能已經看到過使用Date()
函式來記錄經過時間的例子。比如:
const start = new Date();
// ... run code ...
const elapsed = new Date() - start;
然而,Date()
的計算被限制在最接近的毫秒數,並且是基於系統時間。而系統時間可以在任何時候被作業系統更新。
Performance API使用獨立的、高精度的定時器,其可以在幾毫秒的時間內記錄。它還提供其他方式無法記錄的指標,如重定向和DNS查詢時間。
記錄效能指標
如果你可以在某處記錄的話,在客戶端程式碼中記錄效能指標是非常有用的。你可以使用Fetch/XMLHttpRequest請求,或者使用Beacon API,來傳送統計資料到服務端進行分析。
另外,大多數分析系統提供類似的事件API來記錄時間。比如說,Google分析的User Timings API可以透過傳遞類別'pageload'
、變數名'DOMready'
和一個值,來記錄DOMContentLoaded
的時間:
const pageload = performance.getEntriesByType( 'navigation' )[0];
ga('send', 'timing', 'pageload', 'DOMready', pageload.domContentLoadedEventStart);
這個例子使用了Page Navigation Timing API,那麼就從這開始吧。
頁面導航時間
在快速連線上測試你的網站,並不能代表使用者體驗。瀏覽器DevTools的NetWork標籤允許你限制速度,但它不能模擬糟糕的或間歇性的訊號。
Navigation Timing API將單獨的PerformanceNavigationTiming物件放入到效能緩衝區中。它包含有關重定向、載入時間、檔案大小、DOM事件等的資訊。
透過執行以下程式碼來訪問該物件:
const pagePerf = performance.getEntriesByType('navigation');
或者傳遞頁面URL(window.location
)到 getEntriesByName() 方法中,來訪問該物件:
const pagePerf = performance.getEntriesByName(window.location);
兩者都返回一個陣列,該陣列擁有一個具有隻讀屬性的物件的單一元素。比如說:
[
{
name: "<https://site.com/>",
initiatorType: "navigation",
entryType: "navigation",
initiatorType: "navigation",
type: "navigate",
nextHopProtocol: "h2",
startTime: 0
...
}
]
該物件包含資源識別屬性:
屬性 | 描述 |
---|---|
name | 資源URL |
entryType | 效能型別 — "navigation"代表一個頁面,"resource"代表一個資源 |
initiatorType | 啟動下載的資源 — "navigation"代表一個頁面 |
nextHopProtocol | 網路協議 |
serverTiming | PerformanceServerTiming物件陣列 |
注意:performanceServerTiming
的name
、description
和duration
等指標由伺服器響應寫入HTTPServer-Timing頭部。
該物件包括相對於頁面載入開始的以毫秒為單位的資源時間屬性。通常情況下,時間會按照這個順序來展示:
屬性 | 描述 |
---|---|
startTime | 頁面開始獲取時的時間戳,從0開始 |
workerStart | 啟動Service Worker之前的時間戳 |
redirectStart | 首次重定向的時間戳 |
redirectEnd | 收到最後重定向最後一個位元組後的時間戳 |
fetchStart | 資源開始獲取前的時間戳 |
domainLookupStart | DNS查詢前的時間戳 |
domainLookupEnd | DNS查詢後的時間戳 |
connectStart | 建立伺服器連線前的時間戳 |
connectEnd | 建立伺服器連線後的時間戳 |
secureConnectionStart | SSL握手前的時間戳 |
requestStart | 瀏覽器請求前的時間戳 |
responseStart | 瀏覽器收到第一個位元組資料的時間戳 |
responseEnd | 收到最後一個位元組資料後的時間戳 |
duration | 從startTime到responseEnd所經過的時間 |
該物件包括以位元組為單位的下載大小屬性:
屬性 | 描述 |
---|---|
transferSize | 資源大小,包括頭部和主體 |
encodedBodySize | 解壓前的資源主體大小 |
decodedBodySize | 解壓後的資源主體大小 |
最後,該物件包括進一步的導航和DOM事件屬性(在Safari中不可用):
屬性 | 描述 |
---|---|
type | "navigate"、"reload"、"back_forward" |
或者 "prerender" | |
redirectCount | 重定向的次數 |
unloadEventStart | 前一個檔案的unload事件之前的時間戳 |
unloadEventEnd | 前一個檔案的unload事件之後的時間戳 |
domInteractive | HTML解析和DOM構建完成時的時間戳 |
domContentLoadedEventStart | 執行DOMContentLoaded事件處理器前的時間戳 |
domContentLoadedEventEnd | 執行DOMContentLoaded事件處理器後的時間戳 |
domComplete | DOM構建和DOMContentLoaded事件完成後的時間戳 |
loadEventStart | 頁面load事件發生前的時間戳 |
loadEventEnd | 頁面load事件發生後的時間戳,所有資源已經被下載 |
在頁面完全載入後記錄頁面載入指標的例子如下:
'performance' in window && window.addEventListener('load', () => {
const
pagePerf = performance.getEntriesByName(window.location)[0],
pageDownload = pagePerf.duration,
pageDomComplete = pagePerf.domComplete;
});
頁面資源時間
每當頁面載入圖片、字型、CSS檔案、JavaScript檔案等資產時,Resource Timing API將PerformanceResourceTiming物件放入效能緩衝區中,可以這麼執行:
const resPerf = performance.getEntriesByType('resource');
這樣會返回資源時間的物件陣列。這些屬性與上面顯示的頁面時間相同,但沒有導航和DOM事件資訊。
下面是返回結果的示例:
[
{
name: "<https://site.com/style.css>",
entryType: "resource",
initiatorType: "link",
fetchStart: 150,
duration: 300
...
},
{
name: "<https://site.com/script.js>",
entryType: "resource",
initiatorType: "script",
fetchStart: 302,
duration: 112
...
},
...
]
單一資源可以傳遞資源URL到.getEntriesByName()
方法進行測試:
const resourceTime = performance.getEntriesByName('<https://site.com/style.css>');
這會返回單一元素的陣列:
[
{
name: "<https://site.com/style.css>",
entryType: "resource",
initiatorType: "link",
fetchStart: 150,
duration: 300
...
}
]
可以使用API來報告載入時間以及每個CSS檔案解壓後的大小:
// array of CSS files, load times, and file sizes
const css = performance.getEntriesByType('resource')
.filter(r => r.initiatorType === 'link' && r.name.includes('.css'))
.map(r => ({
name: r.name,
load: r.duration + 'ms',
size: r.decodedBodySize + ' bytes'
}));
CSS陣列現在為每個CSS檔案包含一個物件。比如:
[
{
name: "<https://site.com/main.css>",
load: "155ms",
size: "14304 bytes"
},
{
name: "<https://site.com/grid.css>",
load: "203ms",
size: "5696 bytes"
}
]
注意:load
的大小為0表示該資源已經被快取了。
至少有150個資源指標物件將被記錄到效能緩衝區。你可以用.setResourceTimingBufferSize(N)
方法定義一個指定數字。比如:
// record 500 resources
performance.setResourceTimingBufferSize(500);
現有的指標可以用.clearResourceTimings()
方法清除。
瀏覽器繪製時間
First Contentful Paint (FCP)測量使用者導航到你的頁面後渲染內容所需的時間。Chrome的DevTool的Lighthouse標籤展示了該指標。谷歌認為FCP時間少於兩秒是好的,你的頁面將比75%的頁面展現的更快。
當發生以下兩種情況時,Paint Timing API將兩個記錄也就是兩個PerformancePaintTiming物件推入效能緩衝區:
first-paint
發生:瀏覽器繪製首個畫素,以及first-contentful-paint
發生:瀏覽器繪製首個DOM元素
當執行下面程式碼時,兩個物件以陣列形式返回:
const paintPerf = performance.getEntriesByType('paint');
返回結果示例:
[
{
"name": "first-paint",
"entryType": "paint",
"startTime": 125
},
{
"name": "first-contentful-paint",
"entryType": "paint",
"startTime": 127
}
]
startTime
是相對於初始化頁面載入的時間。
使用者時間
Performance API可以用來為你自己的應用功能計時。所有的使用者時間方法都可以在客戶端的JavaScript、Web Workers、Deno和Node.js中使用。
注意,Node.js指令碼必須載入Performance hooks(perf_hooks
)模組。**
CommonJSrequire
語法:
const { performance } = require('perf_hooks');
或者ESMimport
語法:
import { performance } from 'perf_hooks';
最簡單的選擇是[performance.now()](<https://developer.mozilla.org/docs/Web/API/Performance/now>)
,其會從程式的生命週期開始,返回一個高精度時間戳。
你可以使用performance.now()
作為簡單的計時器。比如說:
const start = performance.now();
// ... run code ...
const elapsed = performance.now() - start;
注意,不標準的timeOrigin
屬性返回一個時間戳。可以用於Node.js和瀏覽器JavaScript,但不能用於IE和Safari。
當管理多個定時器時,performance.now()
很快就變得不切實際。.mark()方法新增一個名為PerformanceMark object物件到效能緩衝區。比如說:
performance.mark('script:start');
performance.mark('p1:start');
// ... run process 1 ...
performance.mark('p1:end');
performance.mark('p2:start');
// ... run process 2 ...
performance.mark('p2:end');
performance.mark('script:end');
下列程式碼返回mark
物件陣列:
const marks = performance.getEntriesByType('mark');
陣列裡的物件擁有entryType
、name
和startTime
屬性:
[
{
entryType: "mark",
name: "script:start",
startTime: 100
},
{
entryType: "mark",
name: "p1:start",
startTime: 200
},
{
entryType: "mark",
name: "p1:end",
startTime: 300
},
...
]
兩個標記之間的時間可以用.measure()方法來計算。它傳遞一個測量名稱,開始標記名稱(或者null
),以及結束標記名稱(或者null
):
performance.measure('p1', 'p1:start', 'p1:end');
performance.measure('script', null, 'script:end');
每次呼叫都會向效能緩衝區推送一個帶有計算持續時間的PerformanceMeasure物件。測量陣列可以透過執行以下程式碼進行訪問:
const measures = performance.getEntriesByType('measure');
返回示例:
[
{
entryType: "measure",
name: "p1",
startTime: 200,
duration: 100
},
{
entryType: "measure",
name: "script",
startTime: 0,
duration: 500
}
]
標記或測量物件可以使用.getEntriesByName()方法按名稱檢索:
performance.getEntriesByName('p1');
其他方法:
- .getEntries():返回所有效能條目的陣列。
- [.clearMarks([name])](https://developer.mozilla.org...):清除指定名稱的標記(不指定名稱則清除所有標記)。
- [.clearMeasures([name])](https://developer.mozilla.org...):清除指定名稱的測量(不指定名稱則清除所有測量)。
PerformanceObserver可以監聽緩衝區的更改,當指定物件出現時執行函式。觀察者函式使用兩個引數定義:
list
:觀察者條目observer
(可選):觀察者物件
function performanceHandler(list, observer) {
list.getEntries().forEach(entry => {
console.log(`name : ${ entry.name }`);
console.log(`type : ${ entry.type }`);
console.log(`duration: ${ entry.duration }`);
// other code, e.g.
// send data via an Ajax request
});
}
該函式傳遞一個新的PerformanceObserver
物件。.observe()方法設定可觀察的entryTypes
(一般來說是mark
,measure
或者resource
):
let observer = new PerformanceObserver(performanceHandler);
observer.observe({entryTypes: ['mark', 'measure']});
每當有新的標記或測量物件被推送到效能緩衝區,performanceHandler()
函式就會執行。
總結
Performance API提供了一種方法來測量網站和應用程式的速度,這些裝置是由不同地點的人在一系列連線上使用的實際裝置。它使每個人都能輕鬆地整理出類似DevTool的指標,並識別潛在的瓶頸。
以上就是本文的全部內容,如果對你有所幫助,歡迎點贊、收藏、轉發~