這是我最近開發碰到的一個問題,本文是我測試出來的實踐結果,供你們參考。html
關於js定時器,setInterval
和setTimeout
,做爲咱們平常開發常用到的方法,你們必定很是熟悉。好比下面一個例子:前端
setInterval(() => {
console.log('1');
}, 500);
複製代碼
做爲剛學前端沒多久的新人也能知道,這段代碼就是每過500ms打印一次1(實際運行還須要考慮js的宏任務和微任務的執行時間,定時器的間隔時間是500ms,可是定時器中的方法觸發可能須要在宏任務隊列中排隊,不必定會在500ms的時候觸發,關於Event Loop的基礎內容不在本文討論以內)。web
可是若是你把瀏覽器從當前頁面切換到另外一個標籤頁,或者把瀏覽器最小化了,這時候,這個頁面定時器的間隔時間仍是500ms?api
本文將測試setInterval
、setTimeout
、requestAnimationFrame
這三個方法在瀏覽器可見以及不可見狀態下的表現,個人測試瀏覽器以及版本是谷歌(86.0.4240.193)
,火狐(81.0.2)
,ie11
。瀏覽器
瀏覽器的可見和不可見狀態的切換會觸發visibilitychange
事件,咱們能夠經過監聽這個事件來判別瀏覽器的可見狀態。性能優化
document.addEventListener("visibilitychange", function() {
console.log(document.visibilityState);
});
複製代碼
document.visibilityState
有三個值websocket
這裏重點關注hidden
這個值,當咱們瀏覽器切換當前頁面到另一個標籤頁或者把瀏覽器最小化的時候,document.visibilityState
就會是hidden
值。咱們也可使用document.hidden
,它返回一個布爾值,爲true
的時候,說明當前瀏覽器是不可見狀態。markdown
關於visibilitychange
的細節能夠看阮一峯老師的這篇文章 Page Visibility API 教程。app
咱們先來測試setInterval
,代碼以下socket
<button id="btn">開始計時</button>
// 兼容ie寫法
document.getElementById('btn').addEventListener('click', function() {
setInterval(function() {
const myDate = new Date();
const currentDate = myDate.getMinutes() + '分'+ myDate.getSeconds() + '秒' + myDate.getMilliseconds() + '豪秒';
// 每次循環打印當前時間
console.log(currentDate);
}, 500);
});
// 瀏覽器可見狀態切換事件
document.addEventListener('visibilitychange', function() {
if(document.hidden) {
console.log('頁面不可見');
}
});
複製代碼
定時器間隔是500ms,先來看下谷歌瀏覽器
咱們發現,當頁面不可見以後,定時器的間隔變成了1s。 接下來,咱們把定時器間隔改爲2s來試下。
先後間隔時間一致。
接下來測試一下火狐
和ie
。這裏列出的圖片都是500ms和2s的例子。
ie瀏覽器
通過我大量的測試,能夠得出結論,谷歌瀏覽器中,當頁面處於不可見狀態時,setInterval的最小間隔時間會被限制爲1s。火狐瀏覽器的setInterval和谷歌特性一致,可是ie瀏覽器沒有對不可見狀態時的setInterval進行性能優化,不可見先後間隔時間不變。
接下來是setTimeout
function timer() {
setTimeout(function() {
const myDate = new Date();
const currentDate = myDate.getMinutes() + '分'+ myDate.getSeconds() + '秒' + myDate.getMilliseconds() + '豪秒';
console.log(currentDate);
timer();
}, 500)
}
// 兼容ie寫法
document.getElementById('btn').addEventListener('click', function() {
timer();
});
複製代碼
一樣先來看看在谷歌瀏覽器中的表現(仍是500ms和2s)
咱們發如今谷歌瀏覽器中,500ms的間隔,setTimeout
和setInterval
表現一致,都是最小間隔限制爲1s。可是2s隔間的測試結果出現了分歧,頁面不可見以後,間隔變成了3s。繼續通過屢次的測試,以下,左圖的間隔時間爲990ms,右圖的間隔時間爲1s。
不可見狀態下,左圖中的990ms間隔時間變爲1s,右圖中的1s間隔時間變爲2s。
咱們再來看看火狐
(500ms和2s)
火狐
瀏覽器不可見狀態下,左圖中的500ms變爲1s,右圖中的2s保持不變。
再來看看ie
瀏覽器(500ms)
同樣毫無優化。
咱們能夠得出結論,在谷歌瀏覽器中,setTimeout在瀏覽器不可見狀態下間隔低於1s的會變爲1s,大於等於1s的會變成N+1s的間隔值。火狐瀏覽器下setTimeout的最小間隔時間會變爲1s,大於等於1s的間隔不變。ie瀏覽器在不可見狀態先後的間隔時間不變。
raf是瀏覽器提供的一個更流暢的處理動畫的方法,它會在下次瀏覽器GUI繪製頁面的時候運行傳入的方法。GUI繪製頁面的頻率跟顯示器的刷新率有關,普通顯示器的刷新率是60hz,所以raf在一秒以內須要運行60次,間隔四捨五入大概是17ms。
function timer() {
const myDate = new Date();
const currentDate = myDate.getMinutes() + '分'+ myDate.getSeconds() + '秒' + myDate.getMilliseconds() + '豪秒';
console.log(currentDate);
window.requestAnimationFrame(timer)
}
// 兼容ie寫法
document.getElementById('btn').addEventListener('click', function() {
timer();
});
複製代碼
咱們來看看不一樣瀏覽器下面的表現:
谷歌瀏覽器
火狐瀏覽器
ie瀏覽器
咱們能夠發現,谷歌瀏覽器和ie瀏覽器當瀏覽器狀態爲不可見時,raf方法將中止執行。火狐瀏覽器當狀態變爲不可見時,會在間隔是1s,2s,4s,8s,16s,32s...這樣的順序下去執行raf方法。
谷歌瀏覽器中,當頁面處於不可見狀態時,setInterval
的最小間隔時間會被限制爲1s。火狐瀏覽器的setInterval
和谷歌特性一致。ie瀏覽器沒有對不可見狀態時的setInterval
進行性能優化,不可見先後間隔時間不變。
在谷歌瀏覽器中,setTimeout
在瀏覽器不可見狀態下間隔低於1s的會變爲1s,大於等於1s的會變成N+1s的間隔值。火狐瀏覽器下setTimeout
的最小間隔時間會變爲1s,大於等於1s的間隔不變。ie瀏覽器在不可見狀態先後的間隔時間不變。
谷歌瀏覽器和ie瀏覽器當瀏覽器狀態爲不可見時,raf
方法將中止執行。火狐瀏覽器當狀態變爲不可見時,會在間隔是1s,2s,4s,8s,16s,32s...這樣的順序下去執行raf
方法。
碰到問題固然須要解決,在一些定時器小於1s的倒計時的頁面中,若是用戶切換到了其餘標籤頁。再切回去的時候,頁面上顯示的倒計時時間實際上是錯誤的,這種隱藏的bug會帶來很大的風險。該怎麼解決呢?
除了調取後臺接口或者websocket
鏈接以外,其實有一個更好的解決方案,webWorkers
。並且webWorkers
還能夠解決一個頁面存在多個定時器時候間隔時間偏差較大的問題。
直接上例子
document.getElementById('btn').addEventListener('click', function() {
var w = new Worker('demo_workers.js');
w.onmessage = function(event){
console.log(event.data);
};
});
//瀏覽器切換事件
document.addEventListener('visibilitychange', function() {
if(document.hidden) {
console.log('頁面不可見');
}
});
複製代碼
// demo_workers.js
setInterval(function() {
const myDate = new Date();
const currentDate = myDate.getMinutes() + '分'+ myDate.getSeconds() + '秒' + myDate.getMilliseconds() + '豪秒';
postMessage(currentDate);
}, 500);
複製代碼
實際結果
間隔保持一致。
若是本文有幫助到你的地方,請幫忙點個贊,感謝支持。