最近接到产品的需求,须要在系统的订单模块-未付费列表上增长一列倒计时显示,订单到期后系统会自动关闭订单,做为开发人员的我实际上是拒绝的,可是你仍是得作吧,除非哪一天我中了彩票,我就能够义正词严的拒绝了(~~~收),回到主题,下面开始个人倒计时进化之旅(期待^^^)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);
}
}
});
}
}
复制代码
经过此次作倒计时功能,让我对setInterval
和setTimeout
和window.requestAnimationFrame
有了更深层次的理解,由于js是单线程,存在事件循环机制,只有当任务队列为空时,才会执行setInterval,setTimeout等宏任务,所以计算时间可能不许确,window.requestAnimationFrame相对来讲性能更好,而且支持GPU加速。windows