Missed the action at this year's Chrome Dev Summit? Catch up with our playlist on YouTube. Watch now.

最佳化 JavaScript 執行

JavaScript 往往是視覺變更的觸發器。 有時是直接透過樣式操作,有時是計算造成視覺變更,例如搜尋或排序一些資料。 時機不對或長時間執行的 JavaScript 可能是常見的效能問題導因,您應該儘量減少它的影響。

TL;DR

  • 針對視覺更新避免 setTimeout 或 setInterval;一律改為使用 requestAnimationFrame。
  • 將長時間執行的 JavaScript 從主執行緒移動至 Web Worker。
  • 使用微任務,以讓 DOM 在多個畫面內變更。
  • 使用 Chrome DevTools 的 Timeline 和 JavaScript Profiler,以評估 JavaScript 的影響。

JavaScript 效能分析可能稱得上是一門藝術,因為您撰寫的 JavaScript 程式碼一點也不像實際執行的程式碼。 最新的瀏覽器使用 JIT 編譯器和各式各樣的最佳化和技巧,以試圖給您儘可能最快的執行速度,這大幅變更了程式碼的動力。

不過話雖如此,您還是可以做一些努力,以協助您的應用程式成功執行 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 的預設 animate 行為是使用 setTimeout!您可以 修補它以使用 requestAnimationFrame,強烈建議使用者採用。

降低複雜性或使用 Web Worker

JavaScript 是在瀏覽器的主執行緒上運作,就連同樣式計算、版面配置,以及在許多情況下與繪製一起運作。 如果您的 JavaScript 長時間執行,它將封鎖這些其他任務,有可能導致畫面遺漏。

關於 JavaScript 的執行時機和持續時間,您應該按戰略計劃安排。 例如,如果您處於如捲動的動畫中,您應該最好要設法將您的 JavaScript 維持在 3-4ms 之間。 比這個數字長的時間,您就可能會佔用太多時間。 如果您處於閒置期,對於所佔用的時間,您可以更寬鬆一些。

在許多情況下,只要 Web Worker 不需要 DOM 存取時,您就可以將純運算工作移轉給 Web Worker。 像排序或搜尋等資料操作或穿越動作,常常正好適合這種模式,也適合載入和模型產生。

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 = e.data;
   // Update data on screen...
});

並非所有的工作都可以適用這種模式:Web Worker 並無 DOM 存取能力。 當您的工作必須在主執行緒上運作時,請考慮批次方案:將較大的任務分割成微任務,每個微任務僅需幾 ms 的時間,同時是跨每個畫面在 requestAnimationFrame 處理常式內執行。

var taskList = breakBigTaskIntoMicroTasks(monsterTaskList);
requestAnimationFrame(processTaskList);

function processTaskList(taskStartTime) {
  var taskFinishTime;

  do {
    // Assume the next task is pushed onto a stack.
    var nextTask = taskList.pop();

    // Process nextTask.
    processTask(nextTask);

    // Go again if there’s enough time to do the next task.
    taskFinishTime = window.performance.now();
  } while (taskFinishTime - taskStartTime < 3);

  if (taskList.length > 0)
    requestAnimationFrame(processTaskList);

}

這種方法會伴隨 UX 和 UI 後果,您將需要確定使用者知道一項任務正在處理中,此時可使用進度或活動指示器。 任一情況下,這種方法將讓您的應用程式的主執行緒保持空閒,協助它對使用者互動保持高回應性。

知曉您 JavaScript 的「畫面負擔」

當評估架構、程式庫或您自己的程式碼時,以個別畫面為基礎,評估執行 JavaScript 程式碼的成本。 在做轉換或捲動等效能關鍵的動畫工作時,這一點尤其重要。

測量您 JavaScript 成本和效能設定檔的最佳方法,是使用 Chrome DevTools。 基本上,您將取得看起來像以下的低詳細資料記錄:

Chrome DevTools 的 Timeline 提供低 JS 執行詳細資料。

如果您發現您有長時間執行的 JavaScript,您可以啟用 DevTools 使用者介面頂部的 JavaScript 分析工具:

啟用 DevTools 中的 JS 分析工具。"><img src="images/optimize-javascript-execution/js-profiler-toggle.jpg" alt="

以這種方式分析 JavaScript 會帶有額外負荷,所以當您想要更深入瞭解 JavaScript 執行期特性時,要確定只啟用此功能。 啟用核取方塊之後,您現在可以執行相同的行為,對於您 JavaScript 呼叫了哪些功能,您會得到多出許多的相關資訊:

Chrome DevTools 的 Timeline 提供高 JS 執行詳細資料。

持有這項資訊,您可以評估 JavaScript 對您的應用程式的效能影響,並開始尋找並修復功能執行所需時間太長的任何熱點。 正如之前所述,您應設法刪除長時間執行的 JavaScript,若不可能的話,就將之移動到 Web Worker ,騰出主執行緒去執行其它任務。

避免微最佳化您的 JavaScript

瀏覽器可以執行一個項目的一種版本會比其他事快 100 倍,例如要求與元素的 offsetTop 比計算 getBoundingClientRect() 快,這當然很好。但事實是每個畫面中,您只會呼叫這類功能幾次,所以著重在 JavaScript 效能的這一方面,通常是浪費精力。 一般情況下,您只會節省 ms 的九牛一毛。

如果您在製作遊戲或高運算成本的的應用程式,那麼就是上述的例外情況,因為在這種情況下,您通常會在單一畫面中納入許多運算,此時所有考量都有幫助。

簡而言之,因為微最佳化通常不會對應至您正在打造的應用程式種類,因此對於微最佳化要非常謹慎。