執行大量 JavaScript 可能會對載入網頁效能和互動性造成負面影響。現代網頁應用程式比以往更依賴 JavaScript,且來源不只一個。包括應用程式程式碼本身 (以及其依賴的架構程式碼),以及新增聊天小工具和影片播放器等功能的第三方指令碼。幕後的數據分析和行銷指令碼也是常見的因素,而且很容易被遺忘。
找出網頁沒有回應的根本原因可能很棘手,需要具備特定專業知識,才能執行網頁效能追蹤或剖析,並解讀結果。此外,實際資料通常與實驗室資料大不相同,因為實驗室資料只會複製一小部分實際使用者情境。這會導致難以找出效能不佳的根本原因,尤其是互動。
Long Animation Frames API 是一種輕量 API,可用於識別實際環境中執行負載過重的 JavaScript。高負載指令碼可能是單一長時間執行的指令碼,也可能是在網頁生命週期內多次執行的指令碼。
導入方式
系統會使用 PerformanceObserver 介面監控長動畫影格。如果動畫影格的轉譯時間超過 50 毫秒,就會發出 long-animation-frame 項目。項目包含長動畫影格的相關資訊,包括影格的時間長度,以及在影格期間執行的指令碼。
long-animation-frame 項目包含 scripts 屬性,這是 PerformanceScript 物件的陣列。每個 PerformanceScript 物件都包含長動畫影格期間執行的指令碼相關資訊,包括指令碼的 sourceURL 和 duration。
找出導致長動畫影格、執行時間最長的指令碼範例
// Accumulate all script entries across the page lifecycle so no
// data is lost between observer callbacks.
const allScripts = [];
const observer = new PerformanceObserver(list => {
// Collect all script entries across frames to find the biggest offenders.
allScripts.push(...list.getEntries().flatMap(entry => entry.scripts));
// Group by sourceURL so you can identify which scripts contribute
// the most total time, even if each individual invocation is short.
const scriptSource = [...new Set(allScripts.map(script => script.sourceURL))];
const scriptsBySource = scriptSource.map(sourceURL => ([sourceURL,
allScripts.filter(script => script.sourceURL === sourceURL)
]));
const processedScripts = scriptsBySource.map(([sourceURL, scripts]) => ({
sourceURL,
count: scripts.length,
totalDuration: scripts.reduce((subtotal, script) => subtotal + script.duration, 0)
}));
// Only include scripts above a certain threshold to reduce noise.
const heavyScripts = processedScripts.filter(script => {
return script.totalDuration > 100;
});
// Sort by total duration so the worst offenders appear first,
// making it easier to prioritize optimization efforts.
heavyScripts.sort((a, b) => b.totalDuration - a.totalDuration);
// Log to the console for local debugging. In production, replace
// this with a call to send the data to your analytics service.
console.table(heavyScripts);
});
// Use buffered: true to capture any long frames that occurred before
// this observer was registered.
observer.observe({type: 'long-animation-frame', buffered: true});
最佳做法
- 建議優先使用 Long Animation Frames API,而非 JS Self-Profiling API 等替代方案,因為後者會產生較高的執行階段負擔。
- 務必摘要重要資訊,因為 Long Animation Frames API 包含許多詳細資料。
- 建議做法:將必要資訊傳送至正式環境中的數據分析服務。
瀏覽器支援和備用策略
不支援 Long Animation Frames API 的瀏覽器會忽略該 API,因此可以安全使用,不需備用方案。在大多數情況下,它找出的效能提升機會也適用於其他瀏覽器。