# 為何你的 Interval 會越跑越慢?閒置狀態頁籤中的 setTimeout、setInterval 發生什麼事 #### 瀏覽器為了避免閒置頁籤浪費資源的種種節流 ### ❓ 前端用 setInterval 寫的時鐘,過一陣子回來越跑越慢了,為什麼? 在背景頁籤中執行的 Timer 會默默消耗莫大的資源,沒在使用的閒置頁籤甚至影響到正在使用的頁籤,為了避免不必要的資源浪費,瀏覽器會針對閒置狀態等頁籤的 Timer 做節流 在 Chrome 上,<span class="blue">無論程式碼中設定的 Interval、Timeout 時間為多少,Chrome 都會強制設定為至少一秒。</span>意指如果你原本程式中寫的是 setInterval() 每 50ms 執行一次,但只要進入其他頁籤,讓該頁籤進入背景時,該 Interval 執行時間會節流至<span class="blue"> 每 1000ms </span>執行一次,<span class="blue">但是只要重新點回該頁籤,Timer 的節流又會取消</span> 在 2021年1月 時,<span class="blue">Chrome 88 </span>以上的版本甚至在特定條件下,該節流時間會被拉長到 <span class="blue">1分鐘</span> 一些後面會提到的術語也先做基本講解: * **隱藏頁籤** 代表有其他頁籤現在是正在被可見的,也就是說使用者切換到其他頁籤,或是瀏覽器被縮到最小化。而瀏覽器的頁籤是能夠透過 [Page visibility api](https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API) 得知的 * **Timer** 代表 Javascript 的 `setTimeout` 以及 `setInterval` ,可以透過 Timer 來安排未來要執行的事件 * **連鎖 Timer** 代表 setTimeout 或 setInterval 是接連執行的,例如 每秒更新時鐘時間 ``` setInterval(() => { clockTime ++; }, 1000); ``` ### 💻 Chrome 節流條件?節流影響? 節流分階段性: #### 🔒 最小節流 只要滿足下述任何條件,就只會有最小程度的節流 * 網頁是可見的(非隱藏頁籤) * 在過去 <span class="blue">30秒</span> 內,網頁有透過任何 發聲API 製造過聲音(無聲的音軌除外) 在這階段下基本上瀏覽器不會做任何節流,除非你的 Timer 的<span class="blue">時間設定低於 4ms,且連鎖次數大於5次時</span>,瀏覽器會將時間設定為至少 4ms #### 🔒 一般節流 這種情況發生於 你的 Timer 不滿足上述情況(意指你的頁籤不為可見狀態,且在過去30秒內頁籤也沒有製造做任何聲音),且任何下述條件成立滿足時: * Timer 連鎖次數低於五次 * 頁籤處在隱藏狀態,但時間低於五分鐘 * 頁籤正在使用 WebRTC(網頁間實現音頻、數據通信的技術) 在這階段下,瀏覽器會節流至每 <span class="blue">1秒</span> 才執行一次。時間接近的 Timer 則會批次一起被進行 #### 🔒 高強度的節流 這部分是 Chrome 88 開始才有的新內容,當你的 Timer 已經不滿足上述兩個節流階段(例如瀏覽器已經閒置至少五分鐘),且滿足以下<span class="blue">所有條件</span>時會發生: * 頁籤已經處於隱藏狀態超過五分鐘 * Timer 連鎖數量超過五次 * 該頁籤已經至少30秒沒發過任何聲音 * 沒有使用 WebRTC 在這階段下,瀏覽器會節流至每 <span class="blue">1分鐘</span> 才執行一次 ### 🔨 實測 在執行以下程式碼時,我預期每秒鐘會印一次當前時間 ``` javascript setInterval(()=>{ const currentTime = new Date().toLocaleTimeString(); console.log(currentTime) }, 1000) ``` 1. 一開始會很正常的每秒印出時間  1. 但當頁籤處在隱藏狀態超過一分鐘後,瀏覽器開始節流,變為 每分鐘執行一次  3. 後面皆為每分鐘執行一次  4. 直到 上午 1:08:52 點回該頁籤,頁籤重新成為可見狀態,時間立刻變回一秒印一次  ### 📌 Workarounds * **Web workers** [Web workers](https://developer.mozilla.org/zh-TW/docs/Web/API/Web_Workers_API/Using_web_workers) 是一種允許網頁在背景[執行緒](https://levelup.gitconnected.com/how-web-browsers-use-processes-and-threads-9f8f8fa23371) 運行的機制,且可以不阻塞主線程正常運行。從[這篇](https://medium.com/@adithyaviswam/overcoming-browser-throttling-of-setinterval-executions-45387853a826)文章所說,上述節流的機制僅會進行在瀏覽器的主執行緒上,所以透過讓 Timer 在 Web workers執行緒上運行則能避免被節流(能看此 [Demo](https://w2or0.csb.app/),利用 Web workers 的 Timer 與 沒有 在 Web workers 上運行的 Timer 差距會越來越大)  * **監聽頁籤狀態** 如果為了避免瀏覽器的 Timer 被節流進而影響到網頁的話,可以在頁籤進入背景時,可以透過監聽原生 Web API 事件 [visibilitychange](https://developer.mozilla.org/zh-CN/docs/Web/API/Document/visibilitychange_event),預先將相關 Timer 中斷 ``` document.addEventListener('visibilitychange', function() { if(document.hidden) { // 頁籤進入背景,把相關 Timer 中斷 } else { // 頁籤進入重新啟用,將 Timer 重啟 } }); ``` ### 參考文獻: * [Overcoming browser throttling of setInterval executions](https://medium.com/@adithyaviswam/overcoming-browser-throttling-of-setinterval-executions-45387853a826) * [Heavy throttling of chained JS timers beginning in Chrome 88](https://developer.chrome.com/blog/timer-throttling-in-chrome-88/) * [What Happens to setTimeout() / setInterval() Timers Running on Inactive Browser Tabs ?](https://usefulangle.com/post/280/settimeout-setinterval-on-inactive-tab) * [https://usefulangle.com/web-updates/post/110/chrome-88-will-throttle-timers-1-minute](https://usefulangle.com/web-updates/post/110/chrome-88-will-throttle-timers-1-minute) <style> .blue { color: #409EFF; } </style>
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up