時間分片

《瀏覽器UI線程更新機制》文章中介紹大多數瀏覽器在 JavaScript 運行時停止 UI 線程隊列中的任務,也就是說 JavaScript 任務必須儘快結束,以免對用戶體驗造成不良影響。

儘管你盡了最大努力,還是有一些 JavaScript 任務因爲複雜性原因不能在 100 毫秒或更少時間內完成。這種情況下,理想方法是讓出對 UI 線程的控制,使 UI 更新可以進行。讓出控制意味着停止 JavaScript 運行,給 UI 線程機會進行更新,然後再繼續運行 JavaScript。於是 JavaScript 定時器進入了我們的視野。

定時器與 UI 線程交互的方式有助於分解長運行腳本成爲較短的片斷。調用 setTimeout() 或 setInterval() 告訴 JavaScript 引擎等待一定時間然後將 JavaScript 任務添加到 UI 隊列中。例如:

function greeting() {
  alert("Hello world!");
}
setTimeout(greeting, 250);

此代碼將在 250 毫秒之後,向 UI 隊列插入一個 JavaScript 任務運行 greeting() 函數。在那個點之前,所有其他 UI 更新和 JavaScript 任務都在運行。請記住,第二個參數指什麼時候應當將任務添加到 UI 隊列之中,並不是說那時代碼將被執行。這個任務必須等到隊列中的其他任務都執行之後才能被執行。

一個常見的長運行腳本就是循環佔用了太長的運行時間。那麼定時器就是你的下一個優化步驟。其基本方法是將循環工作分解到定時器序列中。典型的循環模式如下:

for (var i = 0, len = items.length; i < len; i++) {
  process(items[i]);
}

這樣的循環結構運行時間過長的原因有二,process() 的複雜度,items 的大小,或兩者兼有。

此處理過程必須是同步處理嗎?數據必須按順序處理嗎?如果這兩個回答都是“否”,那麼代碼將適於使用定時器分解工作。一種基本異步代碼模式如下:

function processArray(items, process, callback) {
  var todo = items.concat();
  function updateProgress () {
    if (todo.length > 0) {
      process(todo.shift());
      setTimeout(updateProgress, 25);
    } else {
      callback()
    }
  }
  updateProgress()
}
// 用例
processArray([1, 2, 3, 4], (item) => {
  console.log('當前項', item)
}, () => {
  console.log('執行完成')
})

在 Windows 系統上定時器分辨率爲 15 毫秒,也就是說一個值爲 15 的定時器延時將根據最後一次系統時間刷新而轉換爲 0 或者 15。設置定時器延時小於 15 將在 Internet Explorer 中導致瀏覽器鎖定,所以最小值建議爲 25 毫秒(實際時間是 15 或 30)以確保至少 15 毫秒延遲。

我們通常將一個任務分解成一系列子任務。如果一個函數運行時間太長,那麼查看它是否可以分解成一系列能夠短時間完成的較小的函數。可將一行代碼簡單地看作一個原子任務,多行代碼組合在一起構成一個獨立任務。某些函數可基於函數調用進行拆分。

其實上面代碼存在一個問題,就是定時器的間隔時間問題,我設置的是25毫秒,也就是認爲最快是25毫秒可以完成UI更新,但是如果UI更新比25毫秒還要快,那麼就需要等待一段時間,這個等待時間是沒有必要的,所以希望的是UI更新完成之後就立馬執行。而實現的關鍵是兩個新API。

requestIdleCallback 事件循環空閒期的回調函數

requestAnimationFrame 在UI更新完成之後的回調

所以根據實際情況去選擇API,在我們任務分解的過程中我們可以把定時器改成 requestAnimationFrame,如果考慮兼容問題,那還是使用定時器吧。

function processArray(items, process, callback) {
  var todo = items.concat();
  function updateProgress () {
    if (todo.length > 0) {
      process(todo.shift());
      requestAnimationFrame(updateProgress)
    } else {
      callback()
    }
  }
  updateProgress()
}

解決同步阻塞的方法,通常有兩種: 異步 與 任務分割。而 React Fiber 便是爲了實現任務分割而誕生的。

Fiber 其實可以算是一種編程思想,在其它語言中也有許多應用(Ruby Fiber)。核心思想是任務拆分和協同,主動把執行權交給主線程,使主線程有時間空擋處理其他高優先級任務。當遇到進程阻塞的問題時,任務分割、異步調用和緩存策略是三個顯著的解決思路。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章