Chrome 第 88 版起針對鏈結的 JS 計時器進行大量節流

Jake Archibald
Jake Archibald

Chrome 88 版 (2021 年 1 月) 會於特定情況下,大幅限制鏈結 JavaScript 計時器的隱藏頁面。這麼做可以降低 CPU 使用率,同時降低電池用量。在某些極端案例中,這項變更會改變行為,但計時器經常會在不同的 API 提高效率且更可靠的情況下使用。

好,那真的很難接受術語,有點模糊讓我們深入瞭解...

術語

隱藏的頁面

一般來說,「隱藏」表示其他分頁正在使用中,或視窗已縮到最小,但瀏覽器可能會將網頁內容完全隱藏時視為隱藏狀態。有些瀏覽器比其他瀏覽器更詳細,但您可以隨時使用頁面瀏覽權限 API 追蹤瀏覽器是否認為瀏覽權限有所變更。

JavaScript 計時器

依「JavaScript 計時器」是指 setTimeoutsetInterval,可讓您安排日後的回呼時間。計時器很實用,不會離開,但有時候會在事件更有效且更準確時,用來輪詢狀態。

鏈結計時器

如果在與 setTimeout 回呼相同的工作中呼叫 setTimeout,則第二個叫用為「鏈結」。使用 setInterval 時,每個疊代都是鏈結的一部分。如果編寫程式碼,可能會比較容易理解:

let chainCount = 0;

setInterval(() => {
  chainCount++;
  console.log(`This is number ${chainCount} in the chain`);
}, 500);

且:

let chainCount = 0;

function setTimeoutChain() {
  setTimeout(() => {
    chainCount++;
    console.log(`This is number ${chainCount} in the chain`);
    setTimeoutChain();
  }, 500);
}

節流的運作方式

節流會分階段發生:

最低限度

在符合「任一」條件的情況下,排定的計時器就會發生這種情況:

  • 網頁已可見
  • 網頁在過去 30 秒內發出了雜訊。這可能來自任何音效 API,但無聲的音軌則不在此限。

除非要求的逾時時間少於 4 毫秒,且鏈結計數為 5 以上,在此情況下,逾時設定為 4 毫秒,否則計時器不會進行節流。事實並非如此,瀏覽器已經多年來也做過這一點。

調節

當排定的計時器不適用最低節流,且符合以下「任何」條件時,就會發生上述情況:

  • 鏈結計數小於 5。
  • 網頁已隱藏不到 5 分鐘。
  • 正在使用 WebRTC。具體來說,RTCPeerConnection 包含「開啟」RTCDataChannel 或「使用中」MediaStreamTrack

瀏覽器每秒會檢查這個群組中的計時器一次。由於系統只會檢查每秒一次,因此逾時時間類似的計時器會批次在一起,藉此合併分頁執行程式碼所需的時間。事實並非如此,瀏覽器已有多年的努力發展。

高節流

好的,這是 Chrome 88 的新功能當未設定最低節流節流條件,且符合下列「所有」條件時,已排定的計時器才會發生劇烈節流:

  • 網頁已隱藏超過 5 分鐘。
  • 鏈結數量為 5 以上。
  • 網頁已設為靜音至少 30 秒。
  • 目前未使用 WebRTC。

在這種情況下,瀏覽器會每分鐘檢查這個群組中的計時器一次。與先前類似,這表示計時器會在這些每分鐘檢查中批次一起。

解決方法

通常有更好的替代計時器;或者,計時器可與其他項目結合,對 CPU 和電池續航力更友善。

狀態輪詢

這是計時器最常見 (不當) 的使用,用來持續檢查或輪詢 (以瞭解是否有變更)。在大多數情況下,系統之間會執行對應的push作業,其中項目會指出變更發生的時間,因此您不必持續檢查。看看是否有事件所達成的相同目的。

以下提供一些例子:

如果要在特定時間顯示通知,還可使用通知觸發條件

動畫

動畫是一種視覺元素,因此不應在頁面「隱藏」時使用 CPU 作業時間。

requestAnimationFrame 排程動畫的運作方式比 JavaScript 計時器更好。這項功能會與裝置的刷新率同步,確保您每個可顯示的影格都只會收到一次回呼,而您將獲得建構該影格的時間上限。此外,requestAnimationFrame 會等待頁面顯示,因此在頁面隱藏時不會使用任何 CPU。

如果您可以預先宣告整個動畫,請考慮使用 CSS 動畫網頁動畫 API。這些優點與 requestAnimationFrame 相同,但瀏覽器可執行自動合成等其他最佳化功能,且通常更易於使用。

如果動畫的影格速率偏低 (例如閃爍的遊標),計時器仍然是目前最佳選項,但您可以將計時器與 requestAnimationFrame 結合使用,就能同時達到兩者的效果:

function animationInterval(ms, signal, callback) {
  const start = document.timeline.currentTime;

  function frame(time) {
    if (signal.aborted) return;
    callback(time);
    scheduleFrame(time);
  }

  function scheduleFrame(time) {
    const elapsed = time - start;
    const roundedElapsed = Math.round(elapsed / ms) * ms;
    const targetNext = start + roundedElapsed + ms;
    const delay = targetNext - performance.now();
    setTimeout(() => requestAnimationFrame(frame), delay);
  }

  scheduleFrame(start);
}

使用方式:

const controller = new AbortController();

// Create an animation callback every second:
animationInterval(1000, controller.signal, time => {
  console.log('tick!', time);
});

// And stop it:
controller.abort();

測試

這項變更將全面套用至 Chrome 88 版 (2021 年 1 月)。 這項功能目前開放 50% 的 Chrome Beta 版、開發人員和 Canary 版使用者使用。如要進行測試,請在啟動 Chrome Beta 版、開發人員版或 Canary 版時使用以下指令列旗標:

--enable-features="IntensiveWakeUpThrottling:grace_period_seconds/10,OptOutZeroTimeoutTimersFromThrottling,AllowAggressiveThrottlingWithWebSocket"

grace_period_seconds/10 引數會導致在頁面隱藏的 10 秒後 (而不是整整 5 分鐘) 開始過熱節流,可更輕鬆地查看節流的影響。

日後規劃

由於計時器會導致 CPU 用量過大,因此將繼續說明在不破壞網頁內容的情況下,如何進行節流。此外,我們還會根據用途新增/變更 API。我個人不希望使用 animationInterval,改用有效率的低頻率動畫回呼。如有任何疑問,歡迎透過 Twitter 與我們聯絡

Heather Zabriskie 透過 Unsplash 提供的標題相片。