面试官:前端倒计时有误差怎么解决

前言

去年遇到的一个问题,也是非常经典的面试题了。能聊的东西还蛮多的

倒计时为啥不准

一个最简单的常用倒计时:

const [count, setCount] = useState(0)
let total = 10  // 倒计时10s
const countDown = ()=>{
    if(total > 0){
      setCount(total)
      total--
      setTimeout(countDown ,1000)
    }
  }

稍微有几毫秒的误差,但是问题不大。原因:JavaScript是单线程,setTimeout 的回调函数会被放入事件队列,既然要排队,就可能被前面的任务阻塞导致延迟 。且任务本身从call stack中拿出来执行也要耗时。所以有1000变1002也合理。就算setTimeout的第二个参数设为0,也会有至少有4ms的延迟。

如果切换了浏览器tab,或者最小化了浏览器,那误差就会变得大了。

倒计时10s,实际时间却经过了15s,误差相当大了。(不失为一种穿越时间去到未来的方法)

原因:当页面处于后台时,浏览器会降低定时器的执行频率以节省资源,导致 setTimeout 的延迟增加。切回来后又正常了

目标:解决切换后台导致的倒计时不准问题

<需要看新机会的>

顺便吆喝一句,技术大厂,待遇之类的给的还可以,就是偶尔有加班(放心,加班有加班费)

前、后端/测试,多地有空位,感兴趣的可以​​​​​​​试试机会~~​

解决方案1

监听 visibilitychange 事件,在切回tab时修正。

页面从后台离开或者切回来,都能触发visibilitychange事件。只需在document.visibilityState === 'visible'时去修正时间,删掉旧的计时器,设置正确的计时,计算下一次触发的差值,然后创建新的计时器。

  // 监听页面切换
  useEffect(() => {
    
    const handleVisibilityChange = () => {
        console.log('Page is visible:', document.visibilityState);
        if(document.visibilityState === 'visible'){
          updateCount()
        }
    };

    // 添加事件监听器
    document.addEventListener('visibilitychange', handleVisibilityChange);

    // 清理函数:移除事件监听器
    return () => {
        document.removeEventListener('visibilitychange', handleVisibilityChange);
    };
  }, []);   
// 修正倒计时
  const updateCount = ()=>{
    clearTimeout(timer) // 清除
    const nowStamp = Date.now()
    const pastTime = nowStamp - firstStamp
    
    const remainTime = CountSeconds * 1000 - pastTime

    if(remainTime > 0){
      setCount(Math.floor(remainTime/1000))
      total = Math.floor(remainTime/1000)
      timer = setTimeout(countDown,remainTime%1000)
    }else{
      setCount(0)
      console.log('最后时间:',new Date().toLocaleString(),'总共耗时:', nowStamp-firstStamp)
    }

  }

特点:会跳过一些时刻计数,可能会错过一些关键节点上事件触发。如果长时间离开,误差变大,实际时间结束,倒计时仍在,激活页面时才结束。

解决方案2

修改回调函数,自带修正逻辑,每次执行时都去修正

    // 每次都修正倒计时
    const countDown = ()=>{
      const nowDate = new Date()
      const nowStamp = nowDate.getTime()
      firstStamp = firstStamp || nowStamp
      lastStamp = lastStamp || nowStamp

      const nextTime = firstStamp + (CountSeconds-total) * 1000
      const gap = nextTime - nowStamp ;
      
      // 如果当前时间超过了下一次应该执行的时间,就修正时间
      if(gap < 1){
        clearTimeout(timer)
        if(total == 0){
          setCount(0)
          console.log('最后时间:',nowDate.toLocaleString(),'总共耗时:', nowStamp-firstStamp)
        }else{
          console.log('left',total, 'time:',nowDate.toLocaleString(),'间隔:',nowStamp-lastStamp)
          lastStamp = nowStamp
          setCount(total)
          total--
          countDown()
        }
      }else{
        timer = setTimeout(countDown,gap)
      }
    }

结果:

特性:每个倒计时时刻都触发,最后更新更精准。(顺便一提,edge浏览器后台状态timeout间隔最低是1000)

解决方案3

上面的都依赖Date模块,改本地时间就会爆炸,一切都乱套了。(可以用performance.now 来缺相对值判断时间)

有没有方案让时钟像邓紫棋一样一直倒数的

有的,就是用web worker,单独的线程去计时,不会受切tab影响

ini 代码解读复制代码let intervalId;
let count = 0;
self.onmessage = function (event) {
    const data = event.data; // 接收主线程传递的数据
    console.log('Worker received:', data);
    count = data;
intervalId = setInterval(countDown,1000); // 这里用了interval
};

function countDown() {
    count--
    self.postMessage(count); // 将结果发送回主线程
    if (count == 0) {
        clearInterval(intervalId);
    }
}

javascript 代码解读复制代码const [worker, setWorker] = useState(null);

  // 初始化 Web Worker
  useEffect(() => {
      const myWorker = new Worker(new URL('./worker.js', import.meta.url));
      // 监听 Worker 时钟 返回的消息
      myWorker.onmessage = (event) => {
        // console.log('Main thread received:', event.data);
        const left = event.data
        const nowDate = new Date()
        const nowStamp = nowDate.getTime()
        if(left > 0){
          const gap = nowStamp - lastStamp
          console.log('left',left, 'time:',nowDate.toLocaleString(),'间隔:',gap)
          lastStamp = nowStamp
          setCount(left)
        }else{
          setCount(0)
          console.log('最后时间:',nowDate.toLocaleString(),'总共耗时:', nowStamp-firstStamp)
        }
      };
      setWorker(myWorker);
      // 清理函数:关闭 Worker
      return () => {
          myWorker.terminate();
      };
  }, []);

缺点:worker的缺点 ;优点:精准计时

总结:

方案1 大修正

方案2 小修正

方案3 无修正

三种方式来使倒计时更准确

——转载自作者:水下黑化已放电

全部评论

相关推荐

线上面试1&nbsp;一分钟自我介绍讲了自己的学习情况和校园实践活动,但是没有讲到自己的前端学习经历,因为时间到了我就没继续讲了。2&nbsp;介绍一下#前端#收获最多/影响最深的的项目3&nbsp;如何理解MVVM的开发模式和优势4&nbsp;掌握的框架有哪些5&nbsp;开发工具掌握哪些?&nbsp;包括但不限于IDE这里我只回答了vscode,现在想想可能也包括elementUI,vantUI,pxcook像素大厨,copilot。然后aipfox这种模拟后台数据是不是也算?node.js&nbsp;webpack是不是也可以说,ai辅助编程,vscode内嵌Copilot辅助编程?6&nbsp;对html5的理解我回答可能学习的时候都有学,没有区分h5和之前的差异。这里没回答好,应该回答新增加的更有语义化的标签,比如nav,header之类的。以下是从w3c复制下来的新特性——————————————————————————————————新的语义元素,比如&nbsp;&lt;header&gt;,&nbsp;&lt;footer&gt;,&nbsp;&lt;article&gt;,&nbsp;and&nbsp;&lt;section&gt;。新的表单控件,比如数字、日期、时间、日历和滑块。强大的图像支持(借由&nbsp;&lt;canvas&gt;&nbsp;和&nbsp;&lt;svg&gt;)强大的多媒体支持(借由&nbsp;&lt;video&gt;&nbsp;和&nbsp;&lt;audio&gt;)强大的新&nbsp;API,比如用本地存储取代&nbsp;cookie。————————————————————————————————————7&nbsp;说说react和vue的区别个人正在学习react,对react了解比较浅,回答react的文件格式jsx,react就是template,script,style的vue文件8&nbsp;如何看待AI技术对程序员的影响我说是一把双刃剑、有着正负面影响正面是帮助我们更好地,深入浅出地学习一门技术,负面是现在ai技术已经能够开发中小型,有面临事失业风险。9&nbsp;前端如何做性能优化只说了组件的懒加载,cdn,后来想了想是不是防抖和节流也算是呢?webpack打包压缩代码体积是不是也算,缓存策略是不是也算?能想到很多,但是没有说出来,因为不确定。10&nbsp;前端如何实现本地存储?不记得具体问题了localstorage?面试员没有继续问了。11&nbsp;问会不会jquery不会12&nbsp;js中的eval()函数是否了解?本人学识浅薄,没印象13&nbsp;版本控制会什么?只会git。14&nbsp;接下来前端学习计划先写完毕设,然后边学react,边巩固vue知识,复习前端3大基础知识15&nbsp;反问问问两位面试员对我的建议和评价。一个人说还不错,就没了,我觉得应该是给我个面子,自己没实习经历,框架也只会vue,不要太打击我自信心,给我个台阶下才这么说。工作地点在哪:广州如果有幸通过面试,我会参加什么项目的开发?答:之后会安排,现在不确定。工作时间是怎么样的?答:HR面会和你聊这个问题。————————————————————————————————个人反思:自我介绍时,先介绍前端学习经历,有多余时间在讲讲大学学习情况和其他太过紧张导致思维短路,有些不确定的内容要大胆地说出来,答错当做没回答,答对的话能够加分。其实问题真的比我在牛客网看到的面试问题要简单很多,自己这段时间也准备了很多,可能准备的不太到位,当然也有我的紧张的老毛病。个人真正的第一次面试,自己的实力也确实比不上牛客网上其他的前端求职者,先了解自己的真实水平,再指定计划学吧。
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客企业服务