面試官:前端倒計時有誤差怎么解決?

倒計時為啥不準(zhǔn)

先說原因:JavaScript是單線程,setTimeout (主線程空閑時才會去執(zhí)行任務(wù))的回調(diào)函數(shù)會被放入事件隊列,既然要排隊,就可能被前面的任務(wù)阻塞導(dǎo)致延遲 。且任務(wù)本身從call stack(執(zhí)行棧)中拿出來執(zhí)行也要耗時。所以有1000變1002也合理。就算setTimeout的第二個參數(shù)設(shè)為0,也會有至少有4ms的延遲。

let total = 9  // 倒計時9s
    let preStamp = Date.now();
    let nowStamp = preStamp;
    const countDown = ()=>{
        if(total > 0){
          preStamp = nowStamp
          nowStamp = Date.now();
          total--
          console.log('最后時間:',new Date().toLocaleString(),'總共耗時:', nowStamp - preStamp)
          setTimeout(countDown ,1000)
        }
      }
      countDown()

正常情況下,瀏覽器tab頁是激活或者顯示狀態(tài)下:


image.png

有幾毫秒的誤差也正常,畢竟setTimeout是先把任務(wù)放到任務(wù)隊列里,等主線程空閑時才會去執(zhí)行。

但是有一種情況,就是tab頁或者頁面不顯示或者隱藏時,倒計時函數(shù)的誤差就會非常大:

image.png

原因:當(dāng)頁面處于后臺時,瀏覽器會降低定時器的執(zhí)行頻率以節(jié)省資源,導(dǎo)致 setTimeout 的延遲增加。切回來后又正常了

解決辦法

  1. 監(jiān)聽 visibilitychange 事件,在切回tab時修正(不推薦)
let timer:any = null;
    let total = 20  // 倒計時9s
    let preStamp = Date.now();
    let nowStamp = preStamp;
    useEffect(() => {
        const handleVisibilityChange = () => {
          console.log('Page is visible:', document.visibilityState);
          if(document.visibilityState === 'visible'){
            preStamp = Date.now();
            nowStamp = preStamp;
            countDown()
          }else{
            timer && clearTimeout(timer)
          }
        };

        // 添加事件監(jiān)聽器
        document.addEventListener('visibilitychange', handleVisibilityChange);
        // 清理函數(shù):移除事件監(jiān)聽器
        return () => {
            document.removeEventListener('visibilitychange', handleVisibilityChange);
        };
    })
    const countDown = ()=>{
        if(total > 0){
          preStamp = nowStamp
          nowStamp = Date.now();
          total--
          nowStamp - preStamp && console.log('最后時間:',new Date().toLocaleString(),'總共耗時:', nowStamp - preStamp)
          timer = setTimeout(countDown ,1000)
        }
      }
      countDown()

當(dāng)前代碼是,tab隱藏的時候停掉倒計時,可以保證頁面顯示的時候倒計時還是每一秒執(zhí)行一次(這種情況只能用來控制動畫,如果是有特殊操作,就不能用這個方法了)。有的博主可能是在tab隱藏時調(diào)整倒計時的頻次,就是setTimeout的第二個參數(shù),但是我嘗試了,不停掉計時器,倒計時會一直跑,而且,隱藏的tab頁面時間越久,誤差會越大,沒有任何意思!


image.png
  1. web worker(推薦)
    用web worker,單獨的線程去計時,不會受切tab影響
//worker.ts
let total = 9;
let startTime = Date.now();
let preStamp = Date.now();
let nowStamp = preStamp;
const countDown = () => {
  const elapsed = Math.floor((Date.now() - startTime) / 1000);
  const remaining = total - elapsed;
  if (remaining > 0) {
    preStamp = nowStamp
    nowStamp = Date.now();
    postMessage({cutN: remaining, time: nowStamp - preStamp});
    setTimeout(countDown, 1000);
  } else {
    postMessage(0);
  }
};

countDown();
//index.ts
  const worker = new Worker(new URL('./worker.ts', import.meta.url));
    worker.onmessage = (e) => {
      console.log('剩余時間:', e.data.cutN, '當(dāng)前時間:', new Date().toLocaleString(), e.data.time);
    };

tab頁面隱藏后,倒計時正常執(zhí)行,準(zhǔn)確度也很好;


image.png

//文中有不對或者不嚴謹?shù)牡胤街x謝這正,小的看見會立刻糾正改過,重新做人?。?!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容