关于倒计时的小思考

503 阅读2分钟

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战

关于setTimeout和setInterval

倒计时误差产生原因:

  • setInterval: 每隔n秒回调一次函数,间隔最小是n秒,要是回调函数里面有复杂功能执行,间隔会远大于秒, 还有就是页面休眠时,定时器就会停止
  • setTimeout: 回调函数在n秒之后执行。
  • javascript单线程运行机制,这2个方法都会被线程阻塞
  • reactvue中,diff算法和DOM渲染都在一个主线程中执行。

解决的方案

  1. 开启一个后台线程单独运行定时器 web workerXnip2021-12-01_15-44-19.jpg
  2. 抹平误差值 requestAnimationFrameXnip2021-12-01_15-40-18.jpg

javascript是单线程执行的,所以以上这2个方法都是会被线程阻塞,比如setInterval延迟设置为1000,如果内部的执行是一个耗时超过1秒的操作,那么每次重复执行的时候会造成2个问题:

    1. 执行被阻塞,预期是1000毫秒执行一次,实际上必须在阻塞结束后才执行。
    1. 阻塞时,setInterval回调会被堆积,当阻塞结束后,堆积只会被消费一个,更可怕的是etInterval在被阻塞一次后,后面的所有执行时间间隔都会被打乱,如果被阻塞N次,时间间隔就会越来越乱
const before = Date.now();
let after = Date.now();
setTimeout(() => {
    after = Date.now();
    console.log('时间差值===>', after - before);
}, 1000);
for (let i = 0; i < 1000000000; i++) {}

// 执行三次的结果:
// 时间差值===> 1130
// 时间差值===> 1026
// 时间差值===> 1028

如何解决堆积问题呢?

因为阻塞是不可能被解决的,那么最简单的方法就是把setInterval换成setTimeout,使用一个递归来造成每隔多久执行一次的功能。当然阻塞是无法被解决的,这里的阻塞不仅仅有回调中的,还有浏览器中的方方面面的阻塞,比如用户的一些操作行为,其他定时器等外部的阻塞,所以这也就是无论我们如何做,页面开久了,定时器都会不准,或者说,变慢的根本原因。

Web Worker的使用

juejin.cn/post/703706…

常规方式

index.htnl

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>倒计时</title>
</head>
<body>
    <div>
        <div class='root'>
            距离2022年8月9日:
            <span class="daySpan"></span>
            <span class="hourSpan"></span>
            <span class="minuteSpan"></span>
            <span class="secondSpan"></span>
        </div>
    </div>
</body>
<script src = './countDown.js'></script>
</html>

countDown.js

const daySpan = document.querySelector(".daySpan"),
  hourSpan = document.querySelector(".hourSpan"),
  minuteSpan = document.querySelector(".minuteSpan"),
  srcondSpan = document.querySelector(".secondSpan");
deadLine = new Date("2022-8-9 00:00"); //未来要开始的时间

function countDown() {
  const now = new Date();
  timeRemainning = deadLine - now; // 剩余倒计时
  let day, hour, minute, second;
  if (timeRemainning < 0) {
    return 0;
  }
  second = Math.floor((timeRemainning / 1000) % 60);
  minute = Math.floor((timeRemainning / 1000 / 60) % 60);
  hour = Math.floor((timeRemainning / 1000 / 60 / 60) % 24);
  day = Math.floor(timeRemainning / 1000 / 60 / 60 / 24);

  daySpan.innerHTML = day + "天";
  hourSpan.innerHTML = hour + "时";
  minuteSpan.innerHTML = minute + "分";
  srcondSpan.innerHTML = second + "秒";

  setTimeout(countDown, 1000);
}

countDown();