JS面试必备之如何实现一个精确的倒计时

 更新时间:2024年03月10日 14:11:46   作者:大橘为重07  
又到了金三银四的季节了,面试的各位同学要开始准备起来了,今天主要分享一个在面试中经常被提到的一个面试题:倒计时,希望对大家有所帮助
(福利推荐:【腾讯云】服务器最新限时优惠活动,云服务器1核2G仅99元/年、2核4G仅768元/3年,立即抢购>>>:9i0i.cn/qcloud

(福利推荐:你还在原价购买阿里云服务器?现在阿里云0.8折限时抢购活动来啦!4核8G企业云服务器仅2998元/3年,立即抢购>>>:9i0i.cn/aliyun

又到了金三银四的季节了,面试的各位同学要开始准备起来了,今天主要分享一个在面试中经常被提到的一个面试题:倒计时。其实这个问题不仅是在面试中,也在我们的业务里也会经常用到,所以到底如何才能写好一个倒计时呢?

首先我们在写倒计时的时候必须要考虑到三点:准确性、稳定性、性能。接下来我们来一步一步实现一个准确的定时器。

setInterval

我们先来简单实现一个倒计时的函数:

function example1(leftTime){
	let t = leftTime;
  setInterval(() => {
    t = t - 1000;
    console.log(t);
  }, 1000);
}

example1(10);

可以看到使用setInterval即可,但是setInterval真的准确吗?我们来看一下MDN中的说明:

如果你的代码逻辑执行时间可能比定时器时间间隔要长,建议你使用递归调用了setTimeout 的具名函数。例如,使用 setInterval() 以 5 秒的间隔轮询服务器,可能因网络延迟、服务器无响应以及许多其他的问题而导致请求无法在分配的时间内完成。

简单来说意思就是,js因为是单线程的原因,如果前面有阻塞线程的任务,那么就可能会导致setInterval函数延迟,这样倒计时就肯定会不准确,建议使用setTimeout替换setInterval。

setTimeout

按照上述的建议将setInterval换为setTimeout后,我们来看下代码:

function example2(leftTime) {
  let t = leftTime;
  setTimeout(() => {
    t = t - 1000;
    if (t > 0) {
			console.log(t);
      example2(t);
    }
    console.log(t);
  }, 1000);
}

MDN中也说了,有很多因素会导致 setTimeout 的回调函数执行比设定的预期值更久,比如嵌套超时、非活动标签超时、追踪型脚本的节流、超时延迟等等,详情见developer.mozilla.org/zh-CN/docs/Web/API/setTimeout,总就就是和setInterval差不多,都可能会有延迟执行的时候,这么一来如何提高倒计时的准确性呢?

requestAnimationFrame

这里就不得不提一个新的方法requestAnimationFrame,它是一个浏览器 API,允许以 60 帧/秒 (FPS) 的速率请求回调,而不会阻塞主线程。通过调用 requestAnimationFrame 方法浏览器会在下一次重绘之前执行指定的函数,这样可以确保回调在每一帧之间都能够得到适时的更新。

我们使用requestAnimationFrame结合setTimeout来优化一下之前的代码:

function example3(leftTime) {
  let t = leftTime;
  function start() {
    requestAnimationFrame(() => {
      t = t - 1000;
      setTimeout(() => {
        console.log(t);
        start();
      }, 1000);
    });
  }
  start();
}

这样的话就可以保证我们倒计时的稳定性,使用requestAnimationFrame还有一个好处就是,息屏或者切后台的操作时,requestAnimationFrame是不会继续调用函数的,但是如果只使用setTimeout或者setInterval的话,他们会在后台一直执行,显而易见,requestAnimationFrame更加的节省性能开销。

在切后台或者息屏的实际执行时会发现,虽然性能上好了,但上述的方法在准确性上确出了问题,当回到页面时,倒计时会接着切后台时的时间执行,这样肯定是不对。

diffTime

要解决上述的问题,通过时间差值每次进行对比就可以了。

function example4(leftTime) {
  const now = new Date().getTime();
  function start() {
    requestAnimationFrame(() => {
      const diff = leftTime - (new Date().getTime() - now);
      setTimeout(() => {
        console.log(diff);
        start();
      }, 1000);
    });
  }
  start();
}

上面的代码实现思路其实在实际的业务中已经能够满足我们的使用场景,但是如果想要在面试的时候表现的再好一点其实还是有可以优化空间的,那么还要怎么优化呢?

finally

我们来仔细分析一下,如上图所示,上面代码每次在setTimeout时其实真正执行的时间不可能完全是一秒,可能多也可能少,这样时间一长之后执行到结束时肯定会和实际时间有误差,虽然在秒级的单位不一定能看出来,如果采用毫秒做单位的话肯定会有些许。那么应该如何处理呢?

其实最终的实现思路有也很简单,在每次在执行setTimeout的时候,不要将每次setTimeout的时间都设置为1000ms,而是算出每次执行实际setTimeout中执行的函数话费的时间,根据实际执行的时间算出下次setTimeout需要执行的时间即可。至于下次执行的时间应该怎么算呢?我们来通过简单的一个图表来找出其中的规律,假设下图是每次setTimeout执行的时间分布图所示:

Time时间executionTime实际执行时间diffTime差值nextTime下次执行时间
012002001000
1000900100900
200090001000
30008002001200
40001300100900

通过上面的图表我们可以发现 nextTime = executionTime > nextTime ? 1000 - diffTime : 1000 + diffTime。

还需要注意的时候,一般在实际业务时后端返回给我们剩余时间字段,通常都不会是整秒的,这样我们第一次执行setTimeout的时的执行时间就需要处理一下,在倒计时最后结束时可以大大减少最终的时间误差。

根据上述的思路我们来看一下最终封装出来的react hooks:

const useCountDown = ({ leftTime, ms = 1000, onEnd }) => {
  const countdownTimer = useRef();
  const startTimer = useRef();
  const startTimeRef = useRef(new Date().getTime());
  const nextTimeRef = useRef(leftTime % ms);

  const [count, setCount] = useState(leftTime);

  const clearTimer = () => {
    countdownTimer.current && clearTimeout(countdownTimer.current);
    startTimer.current && clearTimeout(startTimer.current);
  };

  const startCountDown = () => {
    clearTimer();
    const currentTime = new Date().getTime();
    // 每次实际执行的时间
    const executionTime = currentTime - startTimeRef.current;

    // 实际执行时间大于上一次需要执行的时间,说明执行时间多了,差值为多出来的时间,否则为少了的时间
    const diffTime =
      executionTime > nextTimeRef.current
        ? executionTime - nextTimeRef.current
        : nextTimeRef.current - executionTime;

    // 剩余时间减去应该执行的时间
    setCount((count) => {
      const c = count - (count % ms === 0 ? ms : count % ms);
      if (c <= 0) return 0;
      return c;
    });

    // 算出下一次的时间 思路:本次的实际执行时间>下一次执行的时间 ?1000 - diffTime : 1000 + diffTime;
    nextTimeRef.current =
      executionTime > nextTimeRef.current ? ms - diffTime : ms + diffTime;

    // 重置初始时间
    startTimeRef.current = new Date().getTime();

    countdownTimer.current = setTimeout(() => {
      requestAnimationFrame(startCountDown);
    }, nextTimeRef.current);
  };

  useEffect(() => {
    setCount(leftTime);
    startTimer.current = setTimeout(
      startCountDown,
      nextTimeRef.current,
    );
    return () => {
      clearTimer();
    };
  }, [leftTime]);

  useEffect(() => {
    if (count <= 0) {
      clearTimer();
      onEnd && onEnd();
    }
  }, [count]);

  return count;
};

export default useCountDown;

除了上述的优化思路,欢迎大家有更好的想法也可以随时进行探讨~

到此这篇关于JS面试必备之如何实现一个精确的倒计时的文章就介绍到这了,更多相关JS倒计时内容请搜索程序员之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持程序员之家

相关文章

  • 使用next.js开发网址缩短服务的方法

    使用next.js开发网址缩短服务的方法

    这篇文章主要介绍了使用next.js开发网址缩短服务,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06
  • JS正则表达式常见用法实例详解

    JS正则表达式常见用法实例详解

    这篇文章主要介绍了JS正则表达式常见用法,结合实例形式分析了javascript元字符、分组符、修饰符、量词基本含义,并结合具体案例形式分析了javascript正则基本使用技巧,需要的朋友可以参考下
    2018-06-06
  • Openlayers3实现车辆轨迹回放功能

    Openlayers3实现车辆轨迹回放功能

    这篇文章主要介绍了Openlayers3实现车辆轨迹回放功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-09-09
  • JS验证字符串功能

    JS验证字符串功能

    这篇文章主要介绍了JS验证字符串功能实例代码,代码简单易懂,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2017-02-02
  • 全面解析Bootstrap图片轮播效果

    全面解析Bootstrap图片轮播效果

    这篇文章主要介绍了全面解析Bootstrap图片轮播效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2015-12-12
  • 鼠标右击事件代码(asp.net后台)

    鼠标右击事件代码(asp.net后台)

    本程序由一个js文件和aspx文件组成,没有后台CS代码。
    2011-01-01
  • js数组中去除重复值的几种方法

    js数组中去除重复值的几种方法

    这篇文章主要介绍了js数组中去除重复值的几种方法,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-08-08
  • Javascript promise异步编程浅析

    Javascript promise异步编程浅析

    这篇文章主要介绍了Javascript promise异步编程,Promise 是异步编程的一种解决方案,可以替代传统的解决方案–回调函数和事件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-04-04
  • 浅谈微信页面入口文件被缓存解决方案

    浅谈微信页面入口文件被缓存解决方案

    缓存对于前端页面来说,是加速页面加载的利器之一,但也同时带来了很多问题,这篇文章主要介绍了浅谈微信页面入口文件被缓存解决方案,感兴趣的小伙伴们可以参考一下
    2018-09-09

最新评论

?


http://www.vxiaotou.com