優化JavaScript的執行效率

xolong 7年前發布 | 8K 次閱讀 JavaScript開發 JavaScript

頁面里的動畫效果大多是通過JavaScript觸發的。有些是直接修改DOM元素樣式屬性而產生的,有些則是由數據計算而產生的,比如搜索或排序。錯誤的執行時機和太長的時間消耗,是常見的導致JavaScript性能低下的原因。你需要盡量減少這兩方面對你的JavaScript代碼帶來的執行性能的影響。

TL;DR

  • 對于動畫效果的實現,避免使用setTimeout或setInterval,請使用requestAnimationFrame。
  • 把耗時長的JavaScript代碼放到Web Workers中去做。
  • 把DOM元素的更新劃分為多個小任務,分別在多個frame中去完成。
  • 使用Chrome DevTools的Timeline和JavaScript Profiler來分析JavaScript的性能。

JavaScript性能分析是一門藝術活,因為你所寫的JavaScript代碼跟實際執行的代碼完全是兩回事。現代瀏覽器都會使用JIT編譯器和其他優化手段來使你的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);

很多框架和示例代碼都是用 setTimeout 或 setInterval 來實現頁面中的動畫效果。這種實現方式的問題是,你在 setTimeout 或 setInterval 中指定的回調函數的執行時機是無法保證的。它將在這一幀動畫的_某個時間點_被執行,很可能是在幀結束的時候。這就意味這我們可能失去這一幀的信息,也就是發生jank。

事實上,jQuery中 animate 函數就是用 setTimeout 來實現的動畫的!我建議你去給它打個補丁,用 requestAnimationFrame 來取代 setTimeout 。

降低代碼復雜度或者使用Web Workers

JavaScript代碼是運行在瀏覽器的主線程上的。與此同時,瀏覽器的主線程還負責樣式計算、布局,甚至繪制(多數情況下)的工作。可以想象,如果JavaScript代碼運行時間過長,就會阻塞主線程上其他的渲染工作,很可能就會導致幀丟失。

因此,你需要認真規劃一下你的JavaScript程序的運行時機和運行耗時。比如,如果你要在一個動畫(比如頁面滾動)執行過程中運行JavaScript程序,那么理想情況是把這段JavaScript程序的運行耗時控制在3-4毫秒以內。如果長于這個時間,那么就有幀丟失的風險。另一方面,在瀏覽器空閑的時候,你可以有更多時間來運行JavaScript程序。

大多數情況下,你可以把純計算工作放到 Web Workers 中做(如果這些計算工作不會涉及DOM元素的存取)。一般來說,JavaScript中的數據處理工作,比如排序或搜索,一般都適合這種處理方式。

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...
});

正如前面提到的,并不是所有的JavaScript代碼都適合這種方式,因為Web Workers無法訪問DOM元素。如果你的JavaScript代碼需要存取DOM元素,也就是說必須在主線程上運行,那么可以考慮批處理的方式:把任務細分為若干個小任務,每個小任務耗時很少,各自放在一個 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,使得用戶能感知到當前瀏覽器正在處理一個任務,比如 使用一個進度條或者指示器 。不管怎樣,這種方式能使釋放瀏覽器的主線程,使你的web應用總能對用戶交互保持響應。

了解JavaScript的“幀稅”

當我們評價一個框架、庫或者自己寫的JavaScript代碼時,很重要的一點就是要分析每一幀中JavaScript代碼運行的消耗。對性能很敏感的動畫效果(比如漸變或滾動)來說,這一點尤其重要。

對于JavaScript代碼的性能分析,最好的方式就是使用Chrom的DevTools。一般來說,通過它你能獲取到這些細節:

如果你發現了運行時間很長的JavaScript代碼,那么你可以開啟DevTools中頂部的JavaScript profiler選項:

但是,這個選項本身的運行也會有一些消耗。因此,確保只有在你需要查看更多運行時細節的時候才開啟它。開啟這個選項之后,再執行一次頁面分析動作,你會看到更多細節:

有了這些信息,你就能分析出JavaScript代碼對于頁面渲染性能的影響了,從而發現并修復JavaScript代碼中性能低下的部分。至于如何修復,就像前面說的,你可以刪除它或者把它放到Web Worker中去,以釋放主線程來響應其他任務。

避免對JavaScript代碼進行微優化

對于一個任務,如果換一種實現方式,瀏覽器的執行速度可以快100倍的話,是非常酷的。比如,讀取一個元素的 offsetTop 屬性就比計算它的 getBoundingClientRect() 要快。但一般情況下,在每一幀中運行的JavaScript代碼之中調用這些函數的次數都是有限的。因此,在這些微優化上花再大的精力,整體上JavaScript代碼的性能可能也就獲得若干毫秒的提升。這是不劃算的。

但是,如果你是做一個游戲,或者計算密集型的web應用,那么這條建議可能不適合你。因為你很可能要在一幀中執行很多計算工作,這種情況下需要爭取做一切可能的性能優化。

簡而言之:慎用微優化。因為一般來說它對你的web應用效果不大。

 

來自:http://www.css88.com/archives/6702

 

 本文由用戶 xolong 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!