前端性能監(jiān)控通過埋點、數(shù)據(jù)采集、分析和可視化發(fā)現(xiàn)性能瓶頸并提供改進方向。其5個關(guān)鍵指標(biāo)為:首屏加載時間、白屏?xí)r間、首次可交互時間(tti)、頁面總加載時間和資源加載錯誤率。1. 首屏加載時間可在
中記錄起始時間,在domcontentloaded事件后計算差值;2. 白屏?xí)r間通過mutationobserver監(jiān)聽dom變化并計算起始與首次渲染的時間差;3. tti可通過performancelongtasktiming與事件監(jiān)聽結(jié)合判斷頁面是否完全可交互;4. 頁面總加載時間通過window.onload或performance api獲取完整加載耗時;5. 資源加載錯誤率通過監(jiān)聽onerror事件統(tǒng)計資源加載失敗比例。性能數(shù)據(jù)可通過fetch或xmlhttprequest發(fā)送至監(jiān)控系統(tǒng),以實現(xiàn)持續(xù)優(yōu)化用戶體驗。
前端性能監(jiān)控對于優(yōu)化用戶體驗至關(guān)重要。它就像一個隱形的醫(yī)生,默默守護著你的網(wǎng)站或應(yīng)用,確保它運行流暢、響應(yīng)迅速。
前端性能監(jiān)控主要通過埋點、數(shù)據(jù)采集、分析和可視化來發(fā)現(xiàn)性能瓶頸,并提供改進方向。
前端性能監(jiān)控的5個關(guān)鍵指標(biāo)
立即學(xué)習(xí)“前端免費學(xué)習(xí)筆記(深入)”;
- 首屏加載時間: 用戶第一眼看到頁面的時間。
- 白屏?xí)r間: 從用戶訪問到頁面開始顯示內(nèi)容的時間。
- 首次可交互時間 (TTI): 頁面變得完全可交互的時間。
- 頁面總加載時間: 加載所有資源所需的時間。
- 資源加載錯誤率: 頁面資源加載失敗的比例。
如何使用 JavaScript 監(jiān)控首屏加載時間?
首屏加載時間是用戶體驗的黃金指標(biāo)。你可以通過以下步驟使用 JavaScript 監(jiān)控它:
- 在 中添加時間戳: 在 html 的 部分,盡可能靠前的位置,添加一個內(nèi)聯(lián)的 JavaScript 腳本,記錄頁面開始加載的時間。
<head> <script> window.performanceStartTime = new Date().getTime(); </script> ... </head>
- 在首屏內(nèi)容加載完成后計算時間差: 在首屏內(nèi)容加載完成后,通常是在 DOMContentLoaded 事件觸發(fā)后,或者在首屏圖片加載完成后,計算當(dāng)前時間與 performanceStartTime 的差值。
document.addEventListener('DOMContentLoaded', function() { setTimeout(function() { // 延遲一段時間,確保首屏內(nèi)容渲染完成 const now = new Date().getTime(); const firstScreenTime = now - window.performanceStartTime; console.log('首屏加載時間:', firstScreenTime + 'ms'); // 將數(shù)據(jù)發(fā)送到你的監(jiān)控系統(tǒng) sendPerformanceData({ firstScreenTime: firstScreenTime }); }, 500); // 延遲 500ms,根據(jù)實際情況調(diào)整 }); function sendPerformanceData(data) { // 使用 fetch 或 XMLHttpRequest 發(fā)送數(shù)據(jù) fetch('/api/performance', { method: 'POST', headers: { 'Content-Type': 'application/JSon' }, body: JSON.stringify(data) }).then(response => { // 處理響應(yīng) }).catch(error => { console.error('發(fā)送性能數(shù)據(jù)失敗:', error); }); }
- 考慮使用 Performance API: 更精確的方式是使用 Performance API,它可以提供更詳細(xì)的性能數(shù)據(jù),例如 DNS 查詢時間、TCP 連接時間、請求響應(yīng)時間等。
window.onload = function() { setTimeout(function() { const performance = window.performance; if (!performance) { console.log('Performance API 不可用'); return; } const timing = performance.timing; const firstPaint = timing.domContentLoadedEventEnd - timing.navigationStart; console.log('首屏渲染時間 (domContentLoadedEventEnd - navigationStart):', firstPaint + 'ms'); // 發(fā)送性能數(shù)據(jù) sendPerformanceData({ firstPaint: firstPaint }); }, 500); };
注意事項:
- 延遲:setTimeout 的延遲時間需要根據(jù)實際情況調(diào)整,確保首屏內(nèi)容已經(jīng)渲染完成。
- 準(zhǔn)確性:首屏的定義可能因應(yīng)用而異,需要根據(jù)實際情況選擇合適的時機計算時間差。
- 數(shù)據(jù)發(fā)送:sendPerformanceData 函數(shù)需要根據(jù)你的監(jiān)控系統(tǒng)進行調(diào)整。
如何監(jiān)控白屏?xí)r間?
白屏?xí)r間是指用戶打開頁面到看到任何內(nèi)容的時間間隔。優(yōu)化白屏?xí)r間能顯著改善用戶體驗。
- 埋點: 在 標(biāo)簽中,緊跟在 之前插入一個內(nèi)聯(lián) JavaScript 腳本,記錄頁面開始渲染的時間戳。
<head> <script> window.pageStartTime = new Date().getTime(); </script> </head>
- 計算白屏?xí)r間: 在頁面首次渲染內(nèi)容出現(xiàn)后(例如,在首個 DOM 元素渲染后),立即計算當(dāng)前時間與 pageStartTime 的差值。可以使用 MutationObserver 監(jiān)聽 DOM 變化。
const observer = new MutationObserver(function(mutations) { if (document.body) { // 檢查 body 是否存在,防止過早觸發(fā) const now = new Date().getTime(); const blankScreenTime = now - window.pageStartTime; console.log('白屏?xí)r間:', blankScreenTime + 'ms'); sendPerformanceData({ blankScreenTime: blankScreenTime }); observer.disconnect(); // 停止監(jiān)聽 } }); observer.observe(document.documentElement, { childList: true, subtree: true });
- 數(shù)據(jù)上報: 將計算得到的白屏?xí)r間發(fā)送到你的監(jiān)控系統(tǒng)。
關(guān)鍵點:
- MutationObserver:用于監(jiān)聽 DOM 變化,一旦檢測到頁面開始渲染,立即計算白屏?xí)r間。
- observer.disconnect():停止監(jiān)聽,避免重復(fù)計算。
- 確保在 標(biāo)簽存在后才開始監(jiān)聽,避免在 body 標(biāo)簽還未加載時就觸發(fā)。
首次可交互時間 (TTI) 如何準(zhǔn)確測量?
TTI 是指頁面變得完全可交互的時間,例如用戶可以點擊按鈕、填寫表單等。準(zhǔn)確測量 TTI 比較復(fù)雜,因為它涉及到判斷頁面上的所有關(guān)鍵元素是否都已加載并可響應(yīng)用戶輸入。
- 使用 Performance API 的 PerformanceLongTaskTiming: 監(jiān)聽長時間任務(wù)(Long Tasks),這些任務(wù)會阻塞主線程,影響頁面的交互性。
const observer = new PerformanceObserver((list) => { list.getEntries().forEach((entry) => { console.log('Long Task:', entry.duration, entry.name, entry.startTime); // 可以將 Long Task 的信息發(fā)送到監(jiān)控系統(tǒng),用于分析 TTI }); }); observer.observe({ type: 'longtask', buffered: true });
- 結(jié)合事件監(jiān)聽: 監(jiān)聽關(guān)鍵元素的事件,例如按鈕的 click 事件、輸入框的 input 事件等,判斷這些元素是否能夠響應(yīng)用戶輸入。
const interactiveElements = document.querySelectorAll('button, input, select, textarea'); let ttiDetected = false; interactiveElements.forEach(element => { element.addEventListener('click', function() { if (!ttiDetected) { ttiDetected = true; const now = new Date().getTime(); console.log('TTI:', now + 'ms'); sendPerformanceData({ tti: now }); } }); });
- 使用 Lighthouse 等工具: Lighthouse 可以自動測量 TTI,并提供詳細(xì)的性能分析報告。
挑戰(zhàn):
- TTI 的定義比較模糊,需要根據(jù)實際應(yīng)用場景進行調(diào)整。
- 準(zhǔn)確判斷頁面是否完全可交互比較困難,需要綜合考慮多種因素。
如何監(jiān)控頁面總加載時間和資源加載錯誤率?
頁面總加載時間是指加載所有資源所需的時間。資源加載錯誤率是指頁面資源加載失敗的比例。
- 頁面總加載時間: 使用 window.onload 事件或 Performance API 測量頁面總加載時間。
window.onload = function() { const performance = window.performance; const timing = performance.timing; const pageLoadTime = timing.loadEventEnd - timing.navigationStart; console.log('頁面總加載時間:', pageLoadTime + 'ms'); sendPerformanceData({ pageLoadTime: pageLoadTime }); };
- 資源加載錯誤率: 監(jiān)聽 window.onerror 事件和 img.onerror 等事件,記錄資源加載失敗的次數(shù)。
let errorCount = 0; window.onerror = function(message, source, lineno, colno, error) { console.error('資源加載錯誤:', message, source, lineno, colno, error); errorCount++; }; const images = document.querySelectorAll('img'); images.forEach(img => { img.onerror = function() { console.error('圖片加載失敗:', img.src); errorCount++; }; }); window.addEventListener('load', function() { const totalResources = document.querySelectorAll('link[rel="stylesheet"], script, img').length; const errorRate = errorCount / totalResources; console.log('資源加載錯誤率:', errorRate); sendPerformanceData({ errorRate: errorRate }); });
注意事項:
- 需要統(tǒng)計所有類型的資源加載錯誤,例如 css、JavaScript、圖片等。
- 資源加載錯誤率應(yīng)該盡可能低,否則會嚴(yán)重影響用戶體驗。
如何將性能數(shù)據(jù)發(fā)送到監(jiān)控系統(tǒng)?
將性能數(shù)據(jù)發(fā)送到監(jiān)控系統(tǒng)是性能監(jiān)控的關(guān)鍵一步。可以使用 fetch 或 XMLHttpRequest 發(fā)送數(shù)據(jù)。
function sendPerformanceData(data) { fetch('/api/performance', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }).then(response => { if (!response.ok) { console.error('發(fā)送性能數(shù)據(jù)失敗:', response.status); } }).catch(error => { console.error('發(fā)送性能數(shù)據(jù)失敗:', error); }); }
關(guān)鍵點:
- 選擇合適的 API 端點:/api/performance 只是一個示例,需要根據(jù)你的監(jiān)控系統(tǒng)進行調(diào)整。
- 處理錯誤:需要處理數(shù)據(jù)發(fā)送失敗的情況,例如網(wǎng)絡(luò)錯誤、服務(wù)器錯誤等。
- 數(shù)據(jù)格式:確保數(shù)據(jù)格式與監(jiān)控系統(tǒng)兼容。
通過上述方法,你可以使用 JavaScript 實現(xiàn)前端性能監(jiān)控,并收集關(guān)鍵指標(biāo),從而優(yōu)化你的網(wǎng)站或應(yīng)用,提升用戶體驗。