研究大佬写的倒计时组件(Vue),学到了不少东西

文章讲述了为何使用setTimeout模拟setInterval,以及其背后的原理,包括定时器执行时机的不确定性、任务队列的处理方式,还介绍了如何通过diffTime解决浏览器后台时计时器暂停的问题。最后提到了添加传入到期时间的新功能,并分享了前端开发者的学习资源和社区建设。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

t = (t - ss) / 60;

if (t < 1) return { ss };

const mm = t % 60;

t = (t - mm) / 60;

if (t < 1) return { mm, ss };

const hh = t % 24;

t = (t - hh) / 24;

if (t < 1) return { hh, mm, ss };

const dd = t;

return { dd, hh, mm, ss };

},

好了,问题开始来了!!

3. 为什么要用setTimeout来模拟setInterval的行为?

这里用setInerval不是更方便吗?

setTimeout(function(){··· }, n); // n毫秒后执行function

setInterval(function(){··· }, n); // 每隔n毫秒执行一次function

可以看看setInterval有什么缺点:

再次强调,定时器指定的时间间隔,表示的是何时将定时器的代码添加到消息队列,而不是何时执行代码。所以真正何时执行代码的时间是不能保证的,取决于何时被主线程的事件循环取到,并执行。

setInterval(function, N)

//即:每隔N秒把function事件推到消息队列中

上图可见,setInterval每隔100ms往队列中添加一个事件;100ms后,添加T1定时器代码至队列中,主线程中还有任务在执行,所以等待,some event执行结束后执行T1定时器代码;又过了100ms,T2定时器被添加到队列中,主线程还在执行T1代码,所以等待;又过了100ms,理论上又要往队列里推一个定时器代码,但由于此时T2还在队列中,所以T3不会被添加,结果就是此时被跳过;这里我们可以看到,T1定时器执行结束后马上执行了T2代码,所以并没有达到定时器的效果。

综上所述,setInterval有两个缺点:

  1. 使用setInterval时,某些间隔会被跳过;

  2. 可能多个定时器会连续执行;

可以这么理解:每个setTimeout产生的任务会直接push到任务队列中;而setInterval在每次把任务push到任务队列前,都要进行一下判断(看上次的任务是否仍在队列中)。

因而我们一般用setTimeout模拟setInterval,来规避掉上面的缺点。

4. 为什么要clearTimeout(this.timer)

第二问:为什么要有this.timer && clearTimeout(this.timer);这一句?

假设一个场景:

如图所示,在倒计时的父组件中,有两个按钮,点击活动一就会传入活动一的剩余时间,点击活动二,就会传入活动二的时间。

如果此时倒计时组件正在做活动一的倒计时,然后点击活动二,就要会马上传入新的time,这个时候就需要重新计时。当然,这里并不会重新计时,因为组件的mounted只会执行一次。也就是说this.countDown();只会执行一次,也就是说this.getTime(this.duration);只会执行一次,因此duration还是活动一的时间,怎么办呢?watch派上用场了。

我们来监听duration,如果发现duration变化,说明新的时间time传入组件,这时就要重新调用this.countDown()。

代码如下:

还剩{{day}}天{{hours}}:{{mins}}:{{seconds}}

好了,但是并没有解释上面提出的那个问题:为什么要有this.timer && clearTimeout(this.timer);这一句?

这样,假设现在页面显示的是活动一的时间,这时,执行到setTimeout,在一秒后就会把setTimeout里的回调函数放到任务队列中,注意是一秒后哦!这时,然而,在这一秒的开头,我们点击了活动二按钮,这时候的活动二的时间就会传入倒计时组件中,然后触发countDown(),也就调用this.getTime(this.duration);,然后执行到setTimeout,也会一秒后把回调函数放到任务队列中。

这时,任务队列中就会有两个setTimeout的回调函数了。等待一秒过去,两个回调函数相继执行,我们就会看到页面上的时间一下子背减了2,实际上是很快速地进行了两遍减1的操作。

这就是为什么要添加上this.timer && clearTimeout(this.timer);这一句的原因了。就是要把上一个setTimeout清除掉。

5. 使用 diffTime

当你认为这是一个完美的组件的时候,你想把这个组件用到项目上,假设你也确实用了,而且还上线了,确发现出现了个大问题:当页面打开的时候,倒计时开始了,时间是 还剩1天12:25:25,然后有人给你发微信,你马上切换到微信,回复消息后切回浏览器,发现倒计时时间却还是还剩1天12:25:25。你慌了:你写的代码出现bug了!

这是怎么回事?

出于节能的考虑, 部分浏览器在进入后台时(或者失去焦点时), 会将 setTimeout 等定时任务暂停 待用户回到浏览器时, 才会重新激活定时任务

说是暂停, 其实应该说是延迟, 1s 的任务延迟到 2s, 2s 的延迟到 5s, 实际情况因浏览器而异。

原来如此,看来不能每次都只是减1这么简单了(毕竟你把浏览器切到后台之后setTimeout就冷却了,等几秒后切回,然后执行setTimeout,只是减了一秒而已)。

所以我们需要改写一下getTime方法。

还剩{{day}}天{{hours}}:{{mins}}:{{seconds}}

可以看到,我们在三个位置添加了新的代码。

首先在data了添加了curTime这个变量,然后在执行countDown的时候给curTime赋值Date.now(),也就是当前的时刻,也就是显示在页面上的那个时刻。

然后看修改的第三处代码。可以看到是将-1改成了-diffTime

now 是 setTimeout的回调函数执行的时候的那个时刻。

因而 diffTime 则 表示 当前这个setTimeout的回调函数执行的时刻距离上 页面上的剩余时间上一次变化的时间段。其实也就是 当前这个setTimeout的回调函数执行的时刻距离上 一个setTimeout的回调函数执行的时刻时间段。

可能你还是不太能理解diffTime。举个例子:

你打开了这个倒计时页面,于是执行了countDown,也就是说要执行getTime这个方法了。也就是会马上执行下列的代码。

this.days = dd || 0;

this.hours = hh || 0;

this.mins = mm || 0;

this.seconds = ss || 0;

执行完这些代码页面上就会出现剩余时间。

this.curTime = Date.now(); 就记录下了此刻的时间点。

然后一秒后执行setTimeout里的回调函数:

const now = Date.now(); 记录当前这个setTimeout的回调函数执行的时间点。

const diffTime = Math.floor((now - this.curTime) / 1000); 记录当前这个setTimeout的回调函数执行的时间点距离页面上开始 渲染 剩余时间的 这一段时间。其实此时的diffTime就是=1。

然后this.curTime = now; 将curTime的值变成当前这个setTimeout的回调函数执行的时间点。

this.getTime(duration - diffTime); 其实就是this.getTime(duration - 1);

然后又执行getTime,就会重新执行下面的代码,有渲染了新的剩余时间。

this.days = dd || 0;

this.hours = hh || 0;

this.mins = mm || 0;

this.seconds = ss || 0;

然后一秒后又要执行setTmieout的回调函数,在这一秒还没结束的时候,我们将浏览器切到后台,此时setTimeout冷却了。等5秒后再切回。于是setTmieout的回调函数才得以执行。

这时const now = Date.now(); 记录当前这个setTimeout的回调函数执行的时间点。

而curTime是上一个setTimeout的回调函数执行的时间。

所以const diffTime = Math.floor((now - this.curTime) / 1000);实际上,diffTime的值就是5秒。

因而this.getTime(duration - diffTime); 其实就是this.getTime(duration - 5);

这样就完美解决了因为浏览器切到后台,导致剩余时间不变的问题。

6. 添加新功能:可以传入到期时间。

之前是只能传入剩余时间的,现在希望也支持传入到期时间。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
img

Vue 编码基础

2.1.1. 组件规范

2.1.2. 模板中使用简单的表达式

2.1.3 指令都使用缩写形式

2.1.4 标签顺序保持一致

2.1.5 必须为 v-for 设置键值 key

2.1.6 v-show 与 v-if 选择

2.1.7 script 标签内部结构顺序

2.1.8 Vue Router 规范

Vue 项目目录规范

2.2.1 基础

2.2.2 使用 Vue-cli 脚手架

2.2.3 目录说明

2.2.4注释说明

2.2.5 其他

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

战项目、讲解视频,并且后续会持续更新**

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-360o6ueb-1712556234142)]

Vue 编码基础

2.1.1. 组件规范

2.1.2. 模板中使用简单的表达式

2.1.3 指令都使用缩写形式

2.1.4 标签顺序保持一致

2.1.5 必须为 v-for 设置键值 key

2.1.6 v-show 与 v-if 选择

2.1.7 script 标签内部结构顺序

2.1.8 Vue Router 规范

Vue 项目目录规范

2.2.1 基础

2.2.2 使用 Vue-cli 脚手架

2.2.3 目录说明

2.2.4注释说明

2.2.5 其他

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-ayfQKz6T-1712556234143)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值