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