找出執行負載過重的 JavaScript

網頁功能: 長動畫影格效能項目

執行大量 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,因此可以安全使用,不需備用方案。在大多數情況下,它找出的效能提升機會也適用於其他瀏覽器。