性能 API 簡介

已發表: 2022-08-10

性能 API 測量實時 Web 應用程序在真實用戶設備和網絡連接上的響應能力。 它可以通過以下方式幫助識別客戶端和服務器端代碼中的瓶頸:

  • 用戶計時:客戶端 JavaScript 函數性能的自定義測量
  • 繪製時間:瀏覽器渲染指標
  • 資源計時:資產和 Ajax 調用的加載性能
  • 導航時間:頁面加載指標,包括重定向、DNS 查找、DOM 準備情況等

API 解決了與典型性能評估相關的幾個問題:

  1. 開發人員經常在連接到快速網絡的高端 PC 上測試應用程序。 DevTools 可以模擬速度較慢的設備,但當大多數客戶運行連接到機場 WiFi 的兩年前的移動設備時,它並不總是突出現實世界的問題。
  2. 谷歌分析等第三方選項經常被阻止,導致結果和假設出現偏差。 在某些國家/地區,您可能還會遇到隱私問題。
  3. 性能 API 可以比Date()等方法更好地準確衡量各種指標。

想了解更多關於使用 Performance API 的信息嗎? 從這裡開始... 點擊推文
以下部分描述了您可以使用性能 API 的方式。 建議您了解一些 JavaScript 和頁面加載指標的知識。

性能 API 可用性

大多數現代瀏覽器都支持性能 API——包括 IE10 和 IE11(甚至 IE9 也有有限的支持)。 您可以使用以下方法檢測 API 的存在:

 if ('performance' in window) { // use Performance API }

完全 Polyfill API 是不可能的,所以要小心缺少瀏覽器。 如果您的 90% 的用戶都樂於使用 Internet Explorer 8 進行瀏覽,那麼您只能衡量 10% 的客戶端具有更強大的應用程序。

該 API 可用於 Web Worker,它提供了一種在後台線程中執行複雜計算而無需停止瀏覽器操作的方法。

大多數 API 方法都可以通過標準 perf_hooks 模塊在服務器端 Node.js 中使用:

 // Node.js performance import { performance } from 'node:perf_hooks'; // or in Common JS: const { performance } = require('node:perf_hooks'); console.log( performance.now() );

Deno 提供了標準的性能 API:

 // Deno performance console.log( performance.now() );

您將需要運行具有--allow-hrtime權限的腳本以啟用高分辨率時間測量:

 deno run --allow-hrtime index.js

服務器端性能通常更容易評估和管理,因為它取決於負載、CPU、RAM、硬盤和雲服務限制。 硬件升級​​或流程管理選項(例如 PM2、集群和 Kubernetes)可能比重構代碼更有效。

出於這個原因,以下部分將重點介紹客戶端性能。

自定義性能測量

性能 API 可用於計時應用程序函數的執行速度。 您可能已經使用或遇到過使用Date()的計時功能:

 const timeStart = new Date(); runMyCode(); const timeTaken = new Date() - timeStart; console.log(`runMyCode() executed in ${ timeTaken }ms`);

性能 API 提供了兩個主要好處:

  1. 更好的準確性: Date()測量到最接近的毫秒,但性能 API 可以測量毫秒的分數(取決於瀏覽器)。
  2. 更好的可靠性:用戶或操作系統可以更改系統時間,因此基於Date()的指標並不總是準確的。 這意味著當時鐘向前移動時,您的函數可能會顯得特別慢!

Date()等效項是performance.now() ,它返回一個高分辨率時間戳,當負責創建文檔的進程啟動時(頁面已加載),該時間戳設置為零:

 const timeStart = performance.now(); runMyCode(); const timeTaken = performance.now() - timeStart; console.log(`runMyCode() executed in ${ timeTaken }ms`);

非標準performance.timeOrigin屬性也可以返回從 1970 年 1 月 1 日開始的時間戳,儘管這在 IE 和 Deno 中不可用。

performance.now()在進行多次測量時變得不切實際。 Performance API 提供了一個緩衝區,您可以通過將標籤名稱傳遞給performance.mark()來記錄事件以供以後分析:

 performance.mark('start:app'); performance.mark('start:init'); init(); // run initialization functions performance.mark('end:init'); performance.mark('start:funcX'); funcX(); // run another function performance.mark('end:funcX'); performance.mark('end:app');

可以使用以下方法提取性能緩衝區中所有標記對象的數組:

 const mark = performance.getEntriesByType('mark');

示例結果:

 [ { detail: null duration: 0 entryType: "mark" name: "start:app" startTime: 1000 }, { detail: null duration: 0 entryType: "mark" name: "start:init" startTime: 1001 }, { detail: null duration: 0 entryType: "mark" name: "end:init" startTime: 1100 }, ... ]

performance.measure()方法計算兩個標記之間的時間並將其存儲在性能緩衝區中。 您傳遞一個新的度量名稱、起始標記名稱(或 null 以從頁面加載測量)和結束標記名稱(或 null 以測量當前時間):

 performance.measure('init', 'start:init', 'end:init');

一個 PerformanceMeasure 對像被附加到具有計算持續時間的緩衝區中。 要獲取此值,您可以請求所有度量的數組:

 const measure = performance.getEntriesByType('measure');

或按其名稱請求度量:

 performance.getEntriesByName('init');

示例結果:

 [ { detail: null duration: 99 entryType: "measure" name: "init" startTime: 1001 } ]

使用性能緩衝器

除了標記和測量,性能緩衝區還用於自動記錄導航時間、資源時間和繪製時間(我們將在後面討論)。 您可以獲得緩衝區中所有條目的數組:

 performance.getEntries();

默認情況下,大多數瀏覽器提供一個緩衝區,最多可存儲 150 個資源指標。 這對於大多數評估來說應該足夠了,但是如果需要,您可以增加或減少緩衝區限制:

 // record 500 metrics performance.setResourceTimingBufferSize(500);

可以按名稱清除標記,也可以指定一個空值來清除所有標記:

performance.clearMarks('start:init');

同樣,可以按名稱或空值清除所有度量值:

 performance.clearMeasures();

監控性能緩衝區更新

PerformanceObserver可以監視性能緩衝區的更改並在發生特定事件時運行函數。 如果您使用MutationObserver來響應 DOM 更新或使用IntersectionObserver來檢測元素何時滾動到視口中,那麼語法將會很熟悉。

您必須使用兩個參數定義觀察者函數:

  1. 已檢測到的觀察者條目數組,以及
  2. 觀察者對象。 如有必要,可以調用它的disconnect()方法來停止觀察者。
 function performanceCallback(list, observer) { list.getEntries().forEach(entry => { console.log(`name : ${ entry.name }`); console.log(`type : ${ entry.type }`); console.log(`start : ${ entry.startTime }`); console.log(`duration: ${ entry.duration }`); }); }

該函數被傳遞給一個新的 PerformanceObserver 對象。 它的observe()方法傳遞了一個 Performance buffer entryTypes 數組來觀察:

 let observer = new PerformanceObserver( performanceCallback ); observer.observe({ entryTypes: ['mark', 'measure'] });

在此示例中,添加新標記或度量會運行performanceCallback()函數。 雖然它只在此處記錄消息,但它可用於觸發數據上傳或進行進一步計算。

測量油漆性能

Paint Timing API 僅在客戶端 JavaScript 中可用,並自動記錄對 Core Web Vitals 很重要的兩個指標:

  1. first-paint:瀏覽器已經開始繪製頁面。
  2. first-contentful-paint:瀏覽器繪製了 DOM 內容的第一個重要項,例如標題或圖像。

這些可以從性能緩衝區中提取到一個數組中:

 const paintTimes = performance.getEntriesByType('paint');

小心在頁面完全加載之前運行它; 這些值不會準備好。 要么等待window.load事件,要么使用PerformanceObserver來監控paint entryTypes。

因停機時間和 WordPress 問題而苦苦掙扎? Kinsta 是旨在節省您時間的託管解決方案! 查看我們的功能

示例結果:

 [ { "name": "first-paint", "entryType": "paint", "startTime": 812, "duration": 0 }, { "name": "first-contentful-paint", "entryType": "paint", "startTime": 856, "duration": 0 } ]

緩慢的首次繪製通常是由阻止渲染的 CSS 或 JavaScript 引起的。 如果瀏覽器必須下載大圖像或渲染複雜元素,則與 first-contentful-paint 的差距可能很大。

資源性能測量

圖像、樣式表和 JavaScript 文件等資源的網絡計時會自動記錄到性能緩衝區。 雖然您幾乎無法解決網絡速度問題(除了減小文件大小),但它可以幫助突出資產較大、Ajax 響應緩慢或第三方腳本性能不佳的問題。

可以使用以下方法從緩衝區中提取一組 PerformanceResourceTiming 指標:

 const resources = performance.getEntriesByType('resource');

或者,您可以通過傳遞其完整 URL 來獲取資產的指標:

 const resource = performance.getEntriesByName('https://test.com/script.js');

示例結果:

 [ { connectEnd: 195, connectStart: 195, decodedBodySize: 0, domainLookupEnd: 195, domainLookupStart: 195, duration: 2, encodedBodySize: 0, entryType: "resource", fetchStart: 195, initiatorType: "script", name: "https://test.com/script.js", nextHopProtocol: "h3", redirectEnd: 0, redirectStart: 0, requestStart: 195, responseEnd: 197, responseStart: 197, secureConnectionStart: 195, serverTiming: [], startTime: 195, transferSize: 0, workerStart: 195 } ]

可以檢查以下屬性:

  • 名稱:資源網址
  • entryType :“資源”
  • 發起者類型: 資源是如何被啟動的,例如“腳本”或“鏈接”
  • serverTiming :服務器在 HTTP Server-Timing 標頭中傳遞的一組PerformanceServerTiming對象(您的服務器端應用程序可以將指標發送到客戶端以進行進一步分析)
  • startTime :獲取開始時的時間戳
  • nextHopProtocol : 使用的網絡協議
  • workerStart :啟動 Progressive Web App Service Worker 之前的時間戳(如果請求未被 Service Worker 攔截,則為 0)
  • redirectStart :重定向開始時的時間戳
  • redirectEnd : 最後一個重定向響應的最後一個字節之後的時間戳
  • fetchStart :資源獲取之前的時間戳
  • domainLookupStart : DNS 查詢前的時間戳
  • domainLookupEnd : DNS 查詢後的時間戳
  • connectStart :建立服務器連接之前的時間戳
  • connectEnd : 建立服務器連接後的時間戳
  • secureConnectionStart : SSL 握手之前的時間戳
  • requestStart : 瀏覽器請求資源之前的時間戳
  • responseStart : 瀏覽器接收到第一個字節數據的時間戳
  • responseEnd : 收到最後一個字節或關閉連接後的時間戳
  • duration : startTime 和 responseEnd 之間的差異
  • transferSize :以字節為單位的資源大小,包括標頭和壓縮主體
  • encodeBodySize : 解壓縮前的資源體(以字節為單位)
  • decodedBodySize : 解壓後的資源體,以字節為單位

此示例腳本檢索由 Fetch API 發起的所有 Ajax 請求並返回總傳輸大小和持續時間:

 const fetchAll = performance.getEntriesByType('resource') .filter( r => r.initiatorType === 'fetch') .reduce( (sum, current) => { return { transferSize: sum.transferSize += current.transferSize, duration: sum.duration += current.duration } }, { transferSize: 0, duration: 0 } );

導航性能測量

卸載前一頁和加載當前頁的網絡時間會作為單個PerformanceNavigationTiming對象自動記錄到性能緩衝區。

使用以下方法將其提取到數組中:

 const pageTime = performance.getEntriesByType('navigation');

…或者通過將頁面 URL 傳遞給.getEntriesByName()

 const pageTiming = performance.getEntriesByName(window.location);

這些指標與資源的指標相同,但還包括特定於頁面的值:

  • entryType : 例如“導航”
  • 類型:“navigate”、“reload”、“back_forward”或“prerender”
  • redirectCount : 重定向次數
  • unloadEventStart : 上一個文檔的卸載事件之前的時間戳
  • unloadEventEnd : 上一個文檔的卸載事件之後的時間戳
  • domInteractive : 瀏覽器解析 HTML 並構建 DOM 的時間戳
  • domContentLoadedEventStart :文檔的 DOMContentLoaded 事件觸發之前的時間戳
  • domContentLoadedEventEnd : 文檔的 DOMContentLoaded 事件完成後的時間戳
  • domComplete : DOM 構建和 DOMContentLoaded 事件完成後的時間戳
  • loadEventStart :頁面加載事件觸發之前的時間戳
  • loadEventEnd : 頁面加載事件和所有資產可用後的時間戳

典型問題包括:

  • unloadEventEnddomInteractive之間有很長的延遲。 這可能表明服務器響應緩慢。
  • domContentLoadedEventStartdomComplete之間有很長的延遲。 這可能表明頁面啟動腳本太慢了。
  • domCompleteloadEventEnd之間有很長的延遲。 這可能表明該頁面有太多資源,或者有幾個資源加載時間過長。

性能記錄與分析

性能 API 允許您整理真實世界的使用數據並將其上傳到服務器以進行進一步分析。 您可以使用第三方服務(例如 Google Analytics)來存儲數據,但第三方腳本可能會被阻止或引入新的性能問題。 您可以根據自己的要求定制您自己的解決方案,以確保監控不會影響其他功能。

警惕無法確定統計數據的情況——可能是因為用戶使用舊瀏覽器、阻止 JavaScript 或在公司代理後面。 了解缺少哪些數據可能比基於不完整信息做出假設更有成效。

理想情況下,您的分析腳本不會因運行複雜計算或上傳大量數據而對性能產生負面影響。 考慮利用網絡工作者並儘量減少同步 localStorage 調用的使用。 以後總是可以批量處理原始數據。

最後,要警惕異常值,例如對統計數據產生不利影響的非常快或非常慢的設備和連接。 例如,如果 9 個用戶在 2 秒內加載了一個頁面,但第 10 個用戶的下載時間為 60 秒,則平均延遲接近 8 秒。 更現實的指標是中位數(2 秒)或第 90 個百分位(每 10 個用戶中有 9 個用戶的加載時間為 2 秒或更短)。

概括

Web 性能仍然是開發人員的一個關鍵因素。 用戶希望網站和應用程序能夠在大多數設備上做出響應。 搜索引擎優化也會受到影響,因為谷歌中速度較慢的網站被降級。
開始使用 Performance API 所需了解的一切都在此處點擊推
有很多性能監控工具,但大多數評估服務器端執行速度或使用有限數量的有能力的客戶端來判斷瀏覽器渲染。 性能 API 提供了一種整理真實用戶指標的方法,這是任何其他方式都無法計算的。