## 进化的倒计时

写在前面

最近接到产品的需求,须要在系统的订单模块-未付费列表上增长一列倒计时显示,订单到期后系统会自动关闭订单,做为开发人员的我实际上是拒绝的,可是你仍是得作吧,除非哪一天我中了彩票,我就能够义正词严的拒绝了(~~~收),回到主题,下面开始个人倒计时进化之旅(期待^^^)javascript

工具函数

咱们项目使用的vue,在我开始真正的写业务逻辑前,我会封装一下须要用到的工具函数,没办法呀,要有全局观嘛(墨镜+大金链子.png)css

// src/utils/index.js 
/** * 将时间戳转换成时分秒,例如08:14:20 * @param {number} ms 时间戳 * */
formatTimeStamp(ms) {
  let hours = parseInt(ms / (1000 * 60 * 60)),
    minutes = parseInt((ms % (1000 * 60 * 60)) / (1000 * 60)),
    seconds = parseInt((ms % (1000 * 60)) / 1000);
    hours = hours < 10 ? '0' + hours : hours;
    minutes = minutes < 10 ? '0' + minutes : minutes;
    seconds = seconds < 10 ? '0' + seconds : seconds;
    return `${hours}:${minutes}:${seconds}`
},
复制代码

小试牛刀的初版倒计时

身为web前端的咱们,首先都不用带脑子想的,我确定首选setInterval的,开始撸代码呀:前端

destroyed() {
  window.clearInterval(this.timer);
},

methods: {
/* 获取未付费订单列表数据 */
  fetchData() {
    this.listLoading = true;
    getList("/order_not_pay", query).then(res => {
      this.orderList = res.data.data;
      this.page.total = res.data.total;
      this.listLoading = false;
      if (this.list.length === 0) {
        // 没有数据
        return;
      }
      // 开始倒计时
      this.countDown();
    });
  },

  /* 倒计时 */
  countDown() {
    this.timer = setInterval(this.updateTime, 1000);
  },

  /* 更新时间 */
  updateTime() {
    let DAYS = 259200000, // 订单3天后过时
      endSeconds = 0,
      endTime = 0;
    this.orderList.forEach((item, index) => {
        endSeconds = +new Date(item.created_at) + DAYS - +new Date();
        if (endSeconds <= 0) {
          // 时间到
          Vue.set(item, "endTime", "已逾期");
        } else {
          endTime = formatTimeStamp(endSeconds);
          Vue.set(item, "endTime", endTime);
        }
    });
  }
}
复制代码

因为是项目中的部分代码,其中一些变量没有详细写出来,大体思路就是拿到列表数据后,启用setInterval定时器,每次循环一遍orderList,根据订单建立时间(created_at)加上3天减去当前时间,获得剩余时间,转换格式,我在组件卸载钩子函数中销毁定时器,注意要用windows,以前用this调用就是不起做用,哈哈哈,this指向的是Vue啦。欧拉,提交代码,告诉测试妹子能够测一下了,等待中(应该是没有bug的吧,渍渍...)。vue

按部就班的第二版倒计时

没有bug是不可能滴,公司请你来干吗,不就是写bug的嘛,惭愧惭愧。过了没多久,噩耗传来,测试妹子给我发消息说,你的定时器有问题哦,3天的话倒计时应该从72:00:00开始吧,你的是从72:02:00开始的。哦,世上竟有如此荒唐之事,待我一探究竟再给你答复,我第一个想到的是是否是服务器时间不许,由于以前我在测试服下单的时间就比正确的时间快两分钟,而后我就找到跟我对接的后端,问一下他是否是服务端时间不许哦,后端熟练的打开了终端,敲了几行我不是很懂的命令,最终显示的北京时间是正确的。咦,不科学,我脱口而出,为了寻找真相,我去到测试妹子旁边让她复现一下bug,在她操做的过程当中,我把目光锁定在她屏幕右下角的时间栏,对比了一下个人手机时间,嗨呀,难怪,原来是她的电脑慢了两分钟,找到问题的根源后,回到座位,思考如何解决。 的确我计算取了本地时间,但咱们不能保证用户电脑的时间都是准确的,为此我想到让后端返回当前时间,与本地时间取差值,每次减去这个差值便可。除了这个问题,我还发现了,列表的计时器偶尔会发生卡顿,与是查了一下setInteval和setTimeout相关资料,推荐作法是用setTimeout模拟setInterval,开始撸代码:java

destroyed() {
  window.clearTimeout(this.timer);
},

methods: {
/* 获取未付费订单列表数据 */
  fetchData() {
    this.listLoading = true;
    getList("/order_not_pay", query).then(res => {
      this.orderList = res.data.data;
      this.page.total = res.data.total;
      this.listLoading = false;
      if (this.list.length === 0) {
        // 没有数据
        return;
      }
      // 开始倒计时
      this.countDown();
    });
  },

  /* 倒计时 */
  countDown() {
    this.timer = setTimeout(this.updateTime, 1000);
  },

  /* 更新时间 */
  updateTime() {
    let DAYS = 259200000, // 订单3天后过时
      endSeconds = 0,
      endTime = 0,
      // 计算服务器与本地的时间差
      diffTime = +new Date(this.orderList[0].now_time) - +new Date();
    this.orderList.forEach((item, index) => {
        endSeconds = +new Date(item.created_at) + DAYS - +new Date() - diffTime;
        if (endSeconds <= 0) {
          // 时间到
          Vue.set(item, "endTime", "已逾期");
        } else {
          endTime = formatTimeStamp(endSeconds);
          Vue.set(item, "endTime", endTime);
        }
    });
    this.timer = setTimeout(this.updateTime, 1000);
  }
}
复制代码

相对稳定的第三版倒计时

后来了解到了window.requestAnimationFrame(callback) api,通常用于css动画制做,根据屏幕的刷新频率执行回调函数,通常是1秒钟执行60此,相比用setTimeout而言,效率更高,固然存在兼容问题,目前方案是浏览器支持使用window.requestAnimationFrame,不然采用setTimeout,要使用window.requestAnimationFrame,须要先解决模拟一秒钟执行回调函数,开始撸代码:web

data() {
  timer: null,
  lastTime: 0, // 记录上一次的时间戳
  options: {
    diffTime: 0, // 服务端与本地时间差值
    days: 259200000, // 订单3天后过时
    endSeconds: 0, 
    endTime: 0
  }
},

destroyed() {
  window.clearTimeout(this.timer);
},

methods: {
/* 获取未付费订单列表数据 */
  fetchData() {
    this.listLoading = true;
    getList("/order_not_pay", query).then(res => {
      this.orderList = res.data.data;
      this.page.total = res.data.total;
      this.listLoading = false;
      if (this.list.length === 0) {
        // 没有数据
        return;
      }
      // 计算服务器与本地的时间差
      this.options.diffTime = +new Date(this.list[0].now_time) - +new Date();
      // 当使用requestAnimationFrame时,记录上一次时间戳
      this.lastTime = Date.now();
      // 开始倒计时
      this.countDown();
    });
  },

  /* 倒计时 */
  countDown() {
    if (window.requestAnimationFrame) {
      // 浏览器支持requestAnimationFrame
      window.requestAnimationFrame(this.animationCb);
    } else {
      // 用setTimeout兼容
      this.timer = setTimeout(this.setTimeoutCb, 1000);
    }
  },

  /* setTimeout调函数 */
  setTimeoutCb() {
    this.updateTime();
    this.timer = setTimeout(this.setTimeoutCb, 1000);
  },

  /* requestAnimationFrame回调函数 */
  animationCb() {
    if (Date.now() - this.lastTime >= 1000) {
      this.updateTime();
      this.lastTime = Date.now();
    }
    window.requestAnimationFrame(this.animationCb);
  },

  /* 更新时间 */
  updateTime() {
    this.list.forEach((item, index) => {
      if (item.status == 20) {
        this.options.endSeconds = +new Date(item.created_at) + this.options.days - +new Date() - this.options.diffTime;
        if (this.options.endSeconds <= 0) {
          // 时间到
          Vue.set(item, "endTime", "已逾期");
        } else {
          this.options.endTime = utils.formatTimeStamp( this.options.endSeconds);
          Vue.set(item, "endTime", this.options.endTime);
        }
      }
    });
  }
}
复制代码

总结

经过此次作倒计时功能,让我对setIntervalsetTimeoutwindow.requestAnimationFrame有了更深层次的理解,由于js是单线程,存在事件循环机制,只有当任务队列为空时,才会执行setInterval,setTimeout等宏任务,所以计算时间可能不许确,window.requestAnimationFrame相对来讲性能更好,而且支持GPU加速。windows

相关文章
相关标签/搜索