## 進化的倒計時

寫在前面

最近接到產品的需求,須要在系統的訂單模塊-未付費列表上增長一列倒計時顯示,訂單到期後系統會自動關閉訂單,做爲開發人員的我實際上是拒絕的,可是你仍是得作吧,除非哪一天我中了彩票,我就能夠義正詞嚴的拒絕了(~~~收),回到主題,下面開始個人倒計時進化之旅(期待^^^)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

相關文章
相關標籤/搜索