最佳化 JavaScript 執行作業

JavaScript 經常會觸發視覺變更。有時是直接透過樣式操作,有時則是計算作業導致視覺變化,例如搜尋或排序資料。不當的時間安排或長時間執行的 JavaScript 是常見的效能問題原因。您應盡可能減少其影響。

Paul Lewis

JavaScript 經常會觸發視覺變更。有時是直接透過樣式操作,有時則是計算作業導致視覺變更,例如搜尋或排序資料。時間不對或執行時間過長的 JavaScript 是常見的效能問題原因。您應盡可能減少其影響。

由於您編寫的 JavaScript 與實際執行的程式碼完全不同,因此 JavaScript 效能分析可說是一門藝術。現代瀏覽器會使用 JIT 編譯器和各種最佳化技巧,盡可能以最快的速度執行,這會大幅改變程式碼的動態。

不過,您還是可以採取一些措施,確保應用程式能順利執行 JavaScript。

摘要

  • 請勿使用 setTimeout 或 setInterval 進行視覺更新,請務必改用 requestAnimationFrame。
  • 將長時間執行的 JavaScript 移出主執行緒,改為在 Web Workers 中執行。
  • 使用微型工作,在多個影格中變更 DOM。
  • 使用 Chrome 開發人員工具的時間軸和 JavaScript 分析器,評估 JavaScript 的影響。

使用 requestAnimationFrame 進行視覺變更

當畫面上發生視覺變化時,您希望在瀏覽器的正確時間執行工作,也就是在影格開始時。如要確保 JavaScript 會在影格開始時執行,唯一的方法就是使用 requestAnimationFrame

/**
    * If run as a requestAnimationFrame callback, this
    * will be run at the start of the frame.
    */
function updateScreen(time) {
    // Make visual updates here.
}

requestAnimationFrame(updateScreen);

架構或範例可能會使用 setTimeoutsetInterval 執行動畫等視覺變更,但這會導致回呼在影格某個點 (可能在結尾) 執行,而這通常會導致我們錯過影格,進而造成卡頓。

setTimeout 導致瀏覽器錯過影格。

事實上,jQuery 曾經使用 setTimeout 來執行 animate 行為。在第 3 版中,已變更為使用 requestAnimationFrame。如果您使用的是舊版 jQuery,建議您套用修正程式以使用 requestAnimationFrame

降低複雜度或使用 Web Workers

JavaScript 會在瀏覽器的主要執行緒上執行,與樣式運算、版面配置和 (在許多情況下) 繪製作業並行。如果 JavaScript 執行時間過長,就會阻斷其他工作,可能導致遺漏影格。

您應有策略地決定 JavaScript 的執行時機和執行時間長度。舉例來說,如果您正在製作捲動等動畫,理想情況下應將 JavaScript 維持在 3-4ms 的範圍內。超過這個時間,就可能會佔用太多時間。如果您處於閒置期,可以更輕鬆地處理所需時間。

在許多情況下,如果純粹的運算工作不需要 DOM 存取權,您可以將該工作移至 Web Workers。資料操控或遍歷 (例如排序或搜尋) 通常很適合這個模型,載入和模型產生也是如此。

var dataSortWorker = new Worker("sort-worker.js");
dataSortWorker.postMesssage(dataToSort);

// The main thread is now free to continue working on other things...

dataSortWorker.addEventListener('message', function(evt) {
    var sortedData = evt.data;
    // Update data on screen...
});

並非所有工作都適合這種模式:Web Workers 無法存取 DOM。如果您的工作必須在主執行緒上執行,請考慮採用批次處理方式,將較大型工作劃分為多個微型工作,每個工作最多只需幾毫秒,並在每個影格內的 requestAnimationFrame 處理常式中執行。

這種做法會影響使用者體驗和使用者介面,因此您必須確保使用者知道系統正在處理工作,例如使用進度或活動指標。無論如何,這個方法都會讓應用程式的主執行緒保持空閒,有助於讓應用程式持續回應使用者互動。

瞭解 JavaScript 的「影格稅」

評估架構、程式庫或您自己的程式碼時,請務必評估逐格執行 JavaScript 程式碼的成本。在執行轉場或捲動等對效能至關重要的動畫作業時,這點尤其重要。

如要評估 JavaScript 的成本,Chrome 開發人員工具的「效能」面板是最佳方式。通常會取得類似以下的低層級記錄:

Chrome 開發人員工具中的效能記錄

「Main」部分會提供 JavaScript 呼叫的火焰圖,讓您精確分析哪些函式已呼叫,以及每個函式所需的時間。

有了這項資訊,您就能評估 JavaScript 對應用程式效能的影響,並開始找出並修正任何執行函式所需時間過長的熱點。如先前所述,您應移除長時間執行的 JavaScript,如果無法移除,請將其移至 Web Worker,釋出主執行緒,以便繼續執行其他工作。

請參閱「開始分析執行階段效能」,瞭解如何使用「效能」面板。

避免對 JavaScript 進行微最佳化

雖然瀏覽器執行某個版本的速度比另一個版本快上 100 倍 (例如要求元素的 offsetTop 比計算 getBoundingClientRect() 還要快),但您通常只會在每個影格中呼叫這類函式幾次,因此通常不必專注於 JavaScript 效能的這方面。您通常只會節省幾毫秒的時間。

如果您正在製作遊戲或需要大量運算的應用程式,則可能不適用這項指南,因為您通常會將大量運算作業塞入單一影格,在這種情況下,任何做法都會有所幫助。

簡而言之,您應非常謹慎地進行微最佳化,因為這類最佳化通常不會對應到您建構的應用程式類型。