如何使用 Performance API 讓您的網(wǎng)站更快
本教程介紹了如何使用 Performance API 記錄來(lái)自訪問(wèn)您的應(yīng)用程序的真實(shí)用戶的類似 DevTool 的統(tǒng)計(jì)信息。
使用瀏覽器 DevTools 評(píng)估 Web 應(yīng)用程序性能很有用,但復(fù)制實(shí)際使用情況并不容易。使用不同設(shè)備、瀏覽器和網(wǎng)絡(luò)的不同地點(diǎn)的人都會(huì)有不同的體驗(yàn)。
性能 API 簡(jiǎn)介
在性能API使用緩沖記錄DevTool般在你的網(wǎng)頁(yè)的生命周期的特定點(diǎn)對(duì)象屬性指標(biāo)。這些要點(diǎn)包括:
頁(yè)面導(dǎo)航:記錄頁(yè)面加載重定向、連接、握手、DOM 事件等。
資源加載:記錄圖片、CSS、腳本、Ajax 調(diào)用等資源加載。
Paint metrics:記錄瀏覽器渲染信息。
自定義性能:記錄任意應(yīng)用程序處理時(shí)間以查找慢功能。
所有 API 都在客戶端 JavaScript 中可用,包括Web Workers。您可以使用以下方法檢測(cè) API 支持:
if ('performance' in window) {
// call Performance APIs
}
注意:請(qǐng)注意,盡管實(shí)現(xiàn)了大部分 API,但 Safari 并不支持所有方法。
自定義(用戶)性能 API 也復(fù)制到:
Node.js 內(nèi)置
performance_hook模塊,以及Deno性能 API(使用它的腳本必須在
--allow-hrtime許可下運(yùn)行)。
還Date()不夠好?
您可能已經(jīng)看過(guò)使用該Date()函數(shù)記錄經(jīng)過(guò)時(shí)間的示例。例如:
const start = new Date();
// ... run code ...
const elapsed = new Date() - start;
但是,Date()計(jì)算僅限于最接近的毫秒并基于系統(tǒng)時(shí)間,操作系統(tǒng)可以隨時(shí)更新系統(tǒng)時(shí)間。
Performance API 使用一個(gè)單獨(dú)的、更高分辨率的計(jì)時(shí)器,可以在幾分之一毫秒內(nèi)進(jìn)行記錄。它還提供了以其他方式無(wú)法記錄的指標(biāo),例如重定向和 DNS 查找時(shí)間。
記錄性能指標(biāo)
如果您可以將其記錄在某處,則在客戶端代碼中計(jì)算性能指標(biāo)非常有用。您可以使用 Ajax Fetch / XMLHttpRequest請(qǐng)求或Beacon API將統(tǒng)計(jì)信息發(fā)送到您的服務(wù)器進(jìn)行分析。
或者,大多數(shù)分析系統(tǒng)提供自定義事件類 API 來(lái)記錄時(shí)間。例如,Google Analytics User Timings API可以DOMContentLoaded通過(guò)傳遞類別 ( 'pageload')、變量名稱 ( "DOMready") 和值來(lái)記錄時(shí)間:
const pageload = performance.getEntriesByType( 'navigation' )[0];
ga('send', 'timing', 'pageload', 'DOMready', pageload.domContentLoadedEventStart);
此示例使用頁(yè)面導(dǎo)航計(jì)時(shí) API。所以讓我們從那里開始……
頁(yè)面導(dǎo)航時(shí)序
在快速連接上測(cè)試您的網(wǎng)站不太可能代表用戶體驗(yàn)。瀏覽器 DevTools網(wǎng)絡(luò)選項(xiàng)卡允許您調(diào)節(jié)速度,但它無(wú)法模擬較差或斷斷續(xù)續(xù)的 3G 信號(hào)。
Navigation Timing API 將單個(gè)PerformanceNavigationTiming對(duì)象推送到性能緩沖區(qū)。它包含真實(shí)用戶觀察到的有關(guān)重定向、加載時(shí)間、文件大小、DOM 事件等的信息。
通過(guò)運(yùn)行訪問(wèn)對(duì)象:
const pagePerf = performance.getEntriesByType( 'navigation' );
或者通過(guò)將頁(yè)面 URL ( window.location)傳遞給 來(lái)訪問(wèn)它getEntriesByName() method:
const pagePerf = performance.getEntriesByName( window.location );
兩者都返回一個(gè)包含具有只讀屬性的對(duì)象的單個(gè)元素的數(shù)組。例如:
[
{
name: "https://site.com/",
initiatorType: "navigation",
entryType: "navigation",
initiatorType: "navigation",
type: "navigate",
nextHopProtocol: "h2",
startTime: 0
...
}
]
該對(duì)象包括資源標(biāo)識(shí)屬性:
| 財(cái)產(chǎn) | 描述 |
|---|---|
| 名稱 | 資源網(wǎng)址 |
| 條目類型 | 性能類型——"navigation"對(duì)于頁(yè)面,"resource"對(duì)于資產(chǎn) |
| 發(fā)起者類型 | 啟動(dòng)下載的資源 -"navigation"用于頁(yè)面 |
| 下一跳協(xié)議 | 網(wǎng)絡(luò)協(xié)議 |
| 服務(wù)器定時(shí) | PerformanceServerTiming對(duì)象數(shù)組 |
注意:performanceServerTiming name、description和durationmetrics由服務(wù)器響應(yīng)寫入 HTTPServer-Timing標(biāo)頭。
該對(duì)象包括相對(duì)于頁(yè)面加載開始的以毫秒為單位的資源計(jì)時(shí)屬性。通常按以下順序預(yù)計(jì)時(shí)間:
| 財(cái)產(chǎn) | 描述 |
|---|---|
| 開始時(shí)間 | 獲取開始時(shí)的時(shí)間戳 -0用于頁(yè)面 |
| 工人開始 | 啟動(dòng) Service Worker 之前的時(shí)間戳 |
| 重定向開始 | 第一次重定向的時(shí)間戳 |
| 重定向結(jié)束 | 收到上次重定向的最后一個(gè)字節(jié)后的時(shí)間戳 |
| 獲取開始 | 獲取資源之前的時(shí)間戳 |
| 域查找開始 | DNS 查找前的時(shí)間戳 |
| 域查找結(jié)束 | DNS 查找后的時(shí)間戳 |
| 連接開始 | 建立服務(wù)器連接前的時(shí)間戳 |
| 連接結(jié)束 | 建立服務(wù)器連接后的時(shí)間戳 |
| 安全連接啟動(dòng) | SSL 握手前的時(shí)間戳 |
| 請(qǐng)求開始 | 瀏覽器請(qǐng)求前的時(shí)間戳 |
| 響應(yīng)開始 | 瀏覽器收到第一個(gè)字節(jié)數(shù)據(jù)時(shí)的時(shí)間戳 |
| 響應(yīng)結(jié)束 | 收到最后一個(gè)字節(jié)數(shù)據(jù)后的時(shí)間戳 |
| 期間 | startTime和responseEnd之間經(jīng)過(guò)的時(shí)間 |
該對(duì)象包括以字節(jié)為單位的下載大小屬性:
| 財(cái)產(chǎn) | 描述 |
|---|---|
| 傳輸大小 | 資源大小,包括標(biāo)題和正文 |
| 編碼體尺寸 | 解壓前的資源體大小 |
| 解碼體尺寸 | 解壓后的資源體大小 |
最后,該對(duì)象包括進(jìn)一步的導(dǎo)航和 DOM 事件屬性(在 Safari 中不可用):
| 財(cái)產(chǎn) | 描述 |
|---|---|
| 類型 | 要么"navigate","reload","back_forward"或者"prerender" |
| 重定向計(jì)數(shù) | 重定向次數(shù) |
| 卸載事件開始 | unload上一個(gè)文檔事件之前的時(shí)間戳 |
| 卸載事件結(jié)束 | unload上一個(gè)文檔事件之后的時(shí)間戳 |
| domInteractive | HTML 解析和 DOM 構(gòu)建完成時(shí)的時(shí)間戳 |
| domContentLoadedEventStart | 運(yùn)行 |
| domContentLoadedEventEnd | 運(yùn)行 |
| domComplete | DOM 構(gòu)建和DOMContentLoaded事件完成時(shí)的時(shí)間戳 |
| 加載事件開始 | 頁(yè)面load事件觸發(fā)前的時(shí)間戳 |
| 加載事件結(jié)束 | 頁(yè)面load事件后的時(shí)間戳。下載所有資產(chǎn) |
在頁(yè)面完全加載后記錄頁(yè)面加載指標(biāo)的示例:
'performance' in window && window.addEventListener('load', () => {
const
pagePerf = performance.getEntriesByName( window.location )[0],
pageDownload = pagePerf.duration,
pageDomComplete = pagePerf.domComplete;
});
頁(yè)面資源時(shí)序
PerformanceResourceTiming每當(dāng)頁(yè)面加載圖像、字體、CSS 文件、JavaScript 文件或任何其他項(xiàng)目等資產(chǎn)時(shí),Resource Timing API 都會(huì)將對(duì)象推送到性能緩沖區(qū)。跑步:
const resPerf = performance.getEntriesByType( 'resource' );
這將返回一組資源計(jì)時(shí)對(duì)象。這些具有與上面顯示的頁(yè)面計(jì)時(shí)相同的屬性,但沒(méi)有導(dǎo)航和 DOM 事件信息。
這是一個(gè)示例結(jié)果:
[
{
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
...
},
...
]
可以通過(guò)將其 URL 傳遞給.getEntriesByName()方法來(lái)檢查單個(gè)資源:
const resourceTime = performance.getEntriesByName('https://site.com/style.css');
這將返回一個(gè)包含單個(gè)元素的數(shù)組:
[
{
name: "https://site.com/style.css",
entryType: "resource",
initiatorType: "link",
fetchStart: 150,
duration: 300
...
}
]
您可以使用 API 報(bào)告每個(gè) CSS 文件的加載時(shí)間和解壓縮大小:
// 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數(shù)組現(xiàn)在包含每個(gè) CSS 文件的對(duì)象。例如:
[
{
name: "https://site.com/main.css",
load: "155ms",
size: "14304 bytes"
},
{
name: "https://site.com/grid.css",
load: "203ms",
size: "5696 bytes"
}
]
注意:負(fù)載和大小為零表示資產(chǎn)已被緩存。
至少 150 個(gè)資源指標(biāo)對(duì)象將被記錄到性能緩沖區(qū)。您可以使用.setResourceTimingBufferSize(N)方法定義一個(gè)特定的數(shù)字。例如:
// record 500 resources
performance.setResourceTimingBufferSize(500);
可以使用 清除現(xiàn)有指標(biāo).clearResourceTimings() method。
瀏覽器繪制時(shí)間
First Contentful Paint (FCP)衡量用戶導(dǎo)航到您的頁(yè)面后呈現(xiàn)內(nèi)容所需的時(shí)間。Chrome 的 DevTool Lighthouse 面板的Performance部分顯示了該指標(biāo)。Google 認(rèn)為 FCP 時(shí)間少于 2 秒是好的,并且您的頁(yè)面將比 Web 的 75% 顯示得更快。
在以下情況下,Paint Timing API 將兩個(gè)記錄兩個(gè)PerformancePaintTiming對(duì)象推送到性能緩沖區(qū):
first-paint發(fā)生:瀏覽器繪制第一個(gè)像素,并且
first-contentful-paint發(fā)生:瀏覽器繪制 DOM 內(nèi)容的第一項(xiàng)
運(yùn)行時(shí),兩個(gè)對(duì)象都以數(shù)組形式返回:
const paintPerf = performance.getEntriesByType( 'paint' );
結(jié)果示例:
[
{
"name": "first-paint",
"entryType": "paint",
"startTime": 125
},
{
"name": "first-contentful-paint",
"entryType": "paint",
"startTime": 127
}
]
的開始時(shí)間是相對(duì)于初始頁(yè)面加載。
用戶計(jì)時(shí)
Performance API 可用于為您自己的應(yīng)用程序功能計(jì)時(shí)。所有用戶計(jì)時(shí)方法都可以在客戶端 JavaScript、Web Workers、Deno 和 Node.js 中使用。
請(qǐng)注意,Node.js 腳本必須加載Performance hooks( perf_hooks) 模塊。
CommonJSrequire語(yǔ)法:
const { performance } = require('perf_hooks');
或者 ES 模塊import語(yǔ)法:
import { performance } from 'perf_hooks';
最簡(jiǎn)單的選項(xiàng)是performance.now(),它返回進(jìn)程生命周期開始時(shí)的高分辨率時(shí)間戳。
您可以performance.now()用于簡(jiǎn)單的計(jì)時(shí)器。例如:
const start = performance.now();
// ... run code ...
const elapsed = performance.now() - start;
注意:非標(biāo)準(zhǔn)timeOrigin屬性返回 Unix 時(shí)間的時(shí)間戳。它可以在 Node.js 和瀏覽器 JavaScript 中使用,但不能在 IE 和 Safari 中使用。
performance.now()在管理多個(gè)計(jì)時(shí)器時(shí)很快變得不切實(shí)際。該.mark()方法將一個(gè)命名的PerformanceMark 對(duì)象對(duì)象添加到性能緩沖區(qū)。例如:
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');
以下代碼返回一個(gè)標(biāo)記對(duì)象數(shù)組:
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()方法計(jì)算兩個(gè)標(biāo)記之間經(jīng)過(guò)的時(shí)間。它傳遞了一個(gè)度量名稱、開始標(biāo)記名稱(或null使用零)和結(jié)束標(biāo)記名稱(或null使用當(dāng)前時(shí)間):
performance.measure('p1', 'p1:start', 'p1:end');
performance.measure('script', null, 'script:end');
每次調(diào)用都會(huì)將具有計(jì)算持續(xù)時(shí)間的PerformanceMeasure對(duì)象推送到性能緩沖區(qū)??梢酝ㄟ^(guò)運(yùn)行來(lái)訪問(wèn)一系列度量:
const measures = performance.getEntriesByType( 'measure' );
例子:
[
{
entryType: "measure",
name: "p1",
startTime: 200,
duration: 100
},
{
entryType: "measure",
name: "script",
startTime: 0,
duration: 500
}
]
可以使用以下.getEntriesByName()方法按名稱檢索標(biāo)記或測(cè)量對(duì)象:
performance.getEntriesByName( 'p1' );
其他方法:
.getEntries(): 返回所有性能條目的數(shù)組。.clearMarks( [name] ): 清除命名標(biāo)記(不帶名稱運(yùn)行以清除所有標(biāo)記).clearMeasures( [name] ): 清除已命名的度量(不帶名稱運(yùn)行以清除所有度量)
一個(gè)PerformanceObserver可以觀看更改到緩沖區(qū),并運(yùn)行一個(gè)函數(shù),當(dāng)特定對(duì)象出現(xiàn)。觀察者函數(shù)定義有兩個(gè)參數(shù):
list: 觀察者條目observer(可選):觀察者對(duì)象
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
});
}
這個(gè)函數(shù)被傳遞給一個(gè)新PerformanceObserver對(duì)象。該.observe()方法然后將觀察到的entryTypes(通常"mark","measure"和/或"resource"):
let observer = new PerformanceObserver( performanceHandler );
observer.observe( { entryTypes: [ 'mark', 'measure' ] } );
performanceHandler()每當(dāng)將新標(biāo)記或度量對(duì)象推送到性能緩沖區(qū)時(shí),該函數(shù)就會(huì)運(yùn)行。
自我分析 API
在自我剖析API是關(guān)系到性能API和可以幫助找到低效或不必要的后臺(tái)功能,而無(wú)需手動(dòng)設(shè)置標(biāo)志和措施。
示例代碼:
// new profiler, 10ms sample rate
const profile = await performance.profile({ sampleInterval: 10 });
// ... run code ...
// stop profiler, get trace
const trace = await profile.stop();
跟蹤返回有關(guān)在每個(gè)采樣間隔執(zhí)行的腳本、函數(shù)和行號(hào)的數(shù)據(jù)。重復(fù)引用相同的代碼可能表明進(jìn)一步優(yōu)化是可能的。
API 目前正在開發(fā)中(請(qǐng)參閱Chrome 狀態(tài))并且可能會(huì)發(fā)生變化。
調(diào)整應(yīng)用程序性能
性能 API 提供了一種方法來(lái)衡量網(wǎng)站和應(yīng)用程序在真實(shí)設(shè)備上的網(wǎng)站和應(yīng)用程序速度,這些設(shè)備由真實(shí)的人在不同位置使用一系列連接。它可以輕松地為每個(gè)人整理類似 DevTool 的指標(biāo)并識(shí)別潛在的瓶頸。
解決這些性能問(wèn)題是另一回事,但SitePoint Jump Start Web Performance 一書會(huì)有所幫助。它提供了一系列快餐、簡(jiǎn)單的食譜和改變生活的飲食,讓您的網(wǎng)站更快、響應(yīng)更快。
往期推薦

Vite 太快了,煩死了,是時(shí)候該小睡一會(huì)了。

如何實(shí)現(xiàn)比 setTimeout 快 80 倍的定時(shí)器?

萬(wàn)字長(zhǎng)文!總結(jié)Vue 性能優(yōu)化方式及原理

90 行代碼的 webpack,你確定不學(xué)嗎?
最后
如果你覺(jué)得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:
點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)
歡迎加我微信「huab119」拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...
關(guān)注公眾號(hào)「前端勸退師」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。

