面试官:你先实现个 CountDown 计时器组件吧!

285 阅读12分钟

前言

欢迎关注同名公众号《熊的猫》,文章会同步更新,也可快速加入前端交流群!

最近有个同学在面试时被要求手写一个 CountDown 计时器组件,但可能是因为之前没有了解过,所以思路上没有那么顺畅,过后他询问我应该怎么写(哈哈,我也没写过),于是就有了本篇文章,希望本篇文章对你有所帮助!!!

不难看出,要求手写一个 CountDown 计时器组件 目的无非考察如下几个方面(谁也不知道面试官在想什么):

  • 组件封装能力

    • 组件输入,即对应组件 内部 Props 的 设计 和 考量
    • 组件输出,即对应组件 对外提供 的 属性 或 方法
    • 逻辑复用,即指组件内部逻辑的 可组合性
  • 时间相关的敏感度

    • 倒计时的实现方式有多种,例如 setInterval、setTimeout、requestAnimationFrame 等等,那么哪种更合适?
    • 获取当前时间可以用 Date.now()、performance.now(),那么该怎么选?

setInterval & setTimeout & requestAnimationFrame

倒计时功能必然需要一个不断执行的 异步过程没疑问吧),这可以使用运行时环境提供的 API,即 setInterval、setTimeout、requestAnimationFrame,那么到底该选择谁更合适呢?

下面进行逐个分析!

setInterval

是什么?

setInterval() 方法会重复调用 一个函数执行一个代码片段,在每次调用之间具有固定的时间间隔,并会返回一个 interval ID 用于标识唯一的时间间隔,可通过调用 clearInterval()") 来移除定时器。

共享同一个 ID 池

值得注意的是,setInterval() 和 setTimeout() 是 共享同一个 ID 池 的,所以说 clearInterval() 和 clearTimeout()") 在技术上是可 互换使用 的:

<template>
    <div class="count-down">
            <h1>Count through setInterval:{{ countInterval }}</h1>
                    <button @click="stopInterval">Stopping through clearTimeout</button>

                        &lt;hr&gt;
                        
                                &lt;h1&gt;Count through setTimeout:{{ countTimeout }}&lt;/h1&gt;
                                        &lt;button @click="stopTimeout"&gt;Stopping through clearInterval&lt;/button&gt;
                                            &lt;/div&gt;
                                            &lt;/template&gt;
                                                
                                                &lt;script setup lang='ts'&gt;
                                                import { ref } from 'vue'
                                                
                                                // 1. Example for setInterval
                                                const countInterval = ref(0)
                                                
                                                const IntervalID = setInterval(() =&gt; countInterval.value++, 1000)
                                                
                                                const stopInterval = () =&gt; {
                                                    console.log('ClearTimeout triggered in stopInterval method!')
                                                        clearTimeout(IntervalID)
                                                        }
                                                        
                                                        // 2. Example for setTimeout
                                                        const countTimeout = ref(0)
                                                        let TimeoutID = 0
                                                        
                                                        const addCount = () =&gt; {
                                                            TimeoutID = setTimeout(() =&gt; {
                                                                    countTimeout.value++
                                                                            addCount()
                                                                                }, 1000)
                                                                                }
                                                                                
                                                                                addCount()
                                                                                
                                                                                const stopTimeout = () =&gt; {
                                                                                    console.log('ClearInterval triggered in stopTimeout method!')
                                                                                        clearInterval(TimeoutID)
                                                                                        }
                                                                                        &lt;/script&gt;</code></pre><p></p><p>但为了 <strong>避免代码杂乱无章</strong><strong>保证代码的可维护性</strong>,还是更推荐使用相互匹配的&nbsp;<strong><code>clearInterval()</code></strong>&nbsp;&nbsp;<strong><code>clearTimeout()</code></strong></p><h4>延迟限制</h4><p><strong><code>setInterval()</code></strong> 定时器是产生 <strong>嵌套使用</strong> 时,且 <strong>嵌套超过 <code>5</code> 层深度</strong> 时:</p><ul><li>浏览器将 <strong>自动强制</strong> 设置定时器的 <strong><code>最小时间间隔为 4 毫秒</code></strong></li><li>若尝试将 <strong>深层嵌套</strong> 中调用&nbsp;<strong><code>setInterval()</code></strong>&nbsp;的延迟设定为 <strong><code>小于 4 毫秒</code></strong> 的值,其将 <strong>被固定为 <code>4</code> 毫秒</strong></li></ul><p>浏览器这样的行为会使得 <strong><code>setInterval()</code></strong> 产生延迟性,原因是 <strong>为了减轻嵌套定时器对性能产生的潜在影响</strong></p><h4>setTimeout + 递归 更合适?</h4><p>如果 <strong>代码逻辑执行时间</strong> 可能大于 <strong>定时器时间间隔</strong>,那么建议你使用 <strong>递归调用<code>setTimeout()</code></strong> 的方式来实现。</p><pre><code class="js">(function loop(){
                                                                                           setTimeout(function() {
                                                                                                 // Your logic here
                                                                                                 
                                                                                                       loop();
                                                                                                         }, delay);
                                                                                                         })();</code></pre><p>例如,如果你要使用&nbsp;<strong><code>setInterval()</code></strong>&nbsp;<strong><code>5s</code></strong> 轮询服务器,可能因 <strong>网络延迟、服务器无响应</strong> 或许多其他的问题而导致请求 <strong>无法在指定时间内完成</strong>,因此可能会出现排队的 <strong>XHR</strong> 请求 <strong>没有按顺序返回</strong> 的问题。</p><h3 id="item-0-4">setTimeout</h3><h4>是什么?</h4><p><strong><code>setTimeout()</code></strong> 方法用于设置一个定时器,该定时器在 <strong>定时器到期后</strong> 执行 <strong>一个函数</strong><strong>指定的一段代码</strong>,并且会返回一个 <strong>正整数</strong>&nbsp;<strong><code>timeoutID</code></strong>,表示由&nbsp;<strong><code>setTimeout()</code></strong>&nbsp;调用创建的定时器的编号,可通过调用&nbsp;<a href="https://link.segmentfault.com/?enc=sV53Yu8y%2Fvug99mR4X48DA%3D%3D.hbRDjF5IkboYF3ttrolG448H%2B0RLFepgmf%2F0y6lmP4SyRMzEaTOIT82PXUgSCz0XmFaFnUi1zR0Ja4WWwr2Z9w%3D%3D" rel="nofollow" target="_blank"><strong><code>clearTimeout()</code></strong></a>&nbsp;来取消定时器。</p><h4>最大延时值</h4><p>浏览器内部以 <strong>32 位带符号整数</strong> 存储延时,这会导致如果一个延时大于 <strong><code>2147483647 ms(大约 24.8 天)</code></strong> 时会产生溢出,导致定时器将会被 <strong>立即执行</strong>,这个限制适用于 <strong><code>setInterval()</code></strong><strong><code>setTimeout()</code></strong></p><h4>延时比指定值更长的原因</h4><p>有很多因素会导致 <strong><code>setTimeout</code></strong><strong>回调函数</strong> 执行 <strong>比设定的预期值更久</strong></p><ul><li><p><strong>嵌套超时</strong></p><ul><li>一旦对&nbsp;<strong><code>setTimeout</code></strong>&nbsp;<strong>嵌套调用达到 <code>5</code></strong>,浏览器将强制执行 <strong><code>4 毫秒的最小超时</code></strong></li></ul></li><li><p><strong>非活动标签的超时</strong></p><ul><li>为了优化 <strong>后台标签的加载损耗(如 降低耗电量)</strong>,浏览器会在非活动标签中强制执行一个 <strong>最小的超时延迟</strong></li><li>例如,<strong><code>Firefox</code></strong> 桌面版 和 <strong><code>Chrome</code></strong> 不活动标签都有一个 <strong><code>1s</code></strong> 的最小超时值</li><li>例如,安卓版 <strong><code>Firefox</code></strong> 浏览器对不活动的标签有一个至少 <strong><code>15m</code></strong> 的超时,并可能完全卸载它们</li><li>例如,若标签中包含&nbsp;<a href="https://link.segmentfault.com/?enc=lEHj1xAJPUF9k5WGhZI%2BEw%3D%3D.%2BdpbYGXLsnlTaPbh7kCZBqqC2AdFtNptkQMukhZkC6GJKW4%2B454NM8qZtUt%2BqYFVu85M3thXPvspNealAkVlbQ%3D%3D" rel="nofollow" target="_blank"><strong><code>AudioContext</code></strong></a><strong><code>Firefox</code></strong> 不会对非活动标签进行节流</li></ul></li><li><p><strong>追踪型脚本的节流</strong></p><ul><li>例如,<strong><code>Firefox</code></strong> 对它识别为追踪型脚本的脚本 <strong>实施额外节流</strong>,即当在 <strong>前台运行</strong> 时,<strong>节流的最小延迟是 <code>4ms</code></strong></li><li>当在 <strong>后台标签</strong> 中,<strong>节流的最小延迟是 <code>10000ms(即 10s)</code></strong>,在文档首次加载后 <strong><code>30s</code></strong> 开始生效</li></ul></li><li><p><strong>在加载页面时推迟超时</strong></p><ul><li>当前标签页正在加载时,<strong><code>Firefox</code></strong> 将推迟触发&nbsp;<strong><code>setTimeout()</code></strong>&nbsp;计时器,<strong>直到主线程被认为是空闲</strong> 的(类似于&nbsp;<a href="https://link.segmentfault.com/?enc=K04rv6UHDEGhJaxtFCB4dQ%3D%3D.T4qMrmQ8opInMOzgulItHyLynCO4z59d%2Byr5%2BNaHxw3ojGSDw7ky1ODVafm5kuuibR9Vyk4j1ULj74xDpNaoLs0F9RkTrkXOZKA8WCHNm1k%3D" rel="nofollow" target="_blank"><strong><code>window.requestIdleCallback()</code></strong></a>)或 直到 <strong>加载事件触发完毕</strong>,才开始触发</li></ul></li></ul><h3 id="item-0-5">requestAnimationFrame</h3><h4>是什么?</h4><p><strong><code>window.requestAnimationFrame()</code></strong> &nbsp;会告诉浏览器我们希望执行一个 <strong>动画</strong>,并且要求浏览器在下次 <strong>重绘之前</strong> 调用指定的回调函数 <strong>更新动画</strong>,即每&nbsp;<strong><code>16.67ms</code></strong>&nbsp;执行一次回调函数。</p><h4>回调函数的参数</h4><p>回调方法在会接收到一个 <a href="https://link.segmentfault.com/?enc=YaNAs9JntG4QiYbCfd6G%2Bg%3D%3D.17tVVurpocIbdc3fYlQM7YgT%2Fms%2F8qdlN1Kuny4WbPcTqqsYx1eqW5m%2FHDhygVTWU0ds2uCcAAGnYqRElWooOoN3wxamBCY0Z%2BLwH9iCbq0%3D" rel="nofollow" target="_blank"><strong><code>DOMHighResTimeStamp</code></strong></a>&nbsp;参数,它是一个 <strong>十进制数</strong>,单位为毫秒,最小精度为 <strong><code>1ms(1000μs)</code></strong></p><p><strong>同一帧</strong> 中的 <strong>多个回调函数</strong> 都会接受到一个 <strong>相同的时间戳</strong>,即使在计算上一个回调函数的工作负载期间已经消耗了一些时间,因此要确保总是使用 <strong>第一个参数(或其他一些获取当前时间的方法)</strong> 来计算动画在一帧中的进度,否则动画在 <strong>高刷新率</strong> 的屏幕中会 <strong>运行得更快</strong></p><h4>暂停调用</h4><p>为了提高性能和电池寿命,在大多数浏览器里,当&nbsp;<strong><code>requestAnimationFrame()</code></strong>&nbsp;运行在 <strong>后台标签页</strong><strong>隐藏的&nbsp;<code>&lt;iframe&gt;</code></strong> 里时,<strong><code>requestAnimationFrame()</code></strong>&nbsp;会被暂停调用以提升性能和电池寿命。</p><h3 id="item-0-6">到底选谁?</h3><p>从以上内容来看,似乎没有一个完美的方案呀,这不是更加大难度了?</p><p></p><p>莫慌!既然都不完美,那么也要从矮个子中挑个高个子。</p><p>先不考虑别的,<strong>setInterval</strong><strong>setTimeout</strong> 有一个致命的缺点:</p><ul><li><p><strong>最大延时限制</strong></p><ul><li>延时时间一旦大于 <strong><code>2147483647 ms(约 24.8 天)</code></strong> 时会产生溢出,导致定时器将会被 <strong>立即执行</strong></li></ul></li></ul><p>别的不说,就这个缺点就导致使用它们来做倒计时组件不太现实,难不成不允许用户的倒计时超过 <strong>25 天</strong> 吗?</p><p></p><p>所以这里选择 <strong>requestAnimationFrame() + 递归</strong> 来实现!</p><h2 id="item-0-7">Date.now() &amp; performance.now()</h2><p>同样的道理,获取当前日期的时间戳也有 <strong>Date.now()</strong><strong>performance.now()</strong> 两种方式,又该选谁呢?</p><p></p><h3 id="item-0-8">Date.now()</h3><h4>是什么?</h4><p><strong><code>Date.now()</code></strong> &nbsp;方法返回自 <strong><code>1970 年 1 月 1 日 00:00:00 (UTC)</code></strong> 到当前时间的毫秒数。</p><h4>时间精度被降低</h4><p>为了提供针对定时攻击和指纹追踪的保护,<strong><code>Date.now()</code></strong>&nbsp;的精度可能会根据浏览器的高级设置项目而被取整。</p><p>例如,在 <strong><code>Firefox</code></strong> 中,默认启用&nbsp;<strong><code>privacy.reduceTimerPrecision</code></strong>&nbsp;设置项,在 <strong><code>Firefox 59</code></strong> 中,默认被取整至 <strong><code>20 微秒</code></strong>;在 <strong><code>Firefox 60</code></strong> 中,则被取整至 <strong><code>2 毫秒</code></strong></p><h3 id="item-0-9">performance.now()</h3><h4>是什么?</h4><p><strong><code>performance.now()</code></strong> &nbsp;方法返回一个 <strong>double 类型</strong> 的、用于存储 <strong>毫秒级</strong> 的时间值。</p><h4>获取当前日期时间戳</h4><p><strong><code>performance.now()</code></strong> 主要是用来描述 <strong>离散时间点</strong><strong>一段时间</strong>(两个离散时间点间的时间差),因此它的返回值并不是当 <strong>前日期的时间戳</strong>,即 <strong><code>performance.now()</code> != <code>Date.now()</code></strong></p><p>但可以通过换算的方式得到,即</p><pre><code class="js">Date.now()&nbsp;&nbsp;performance.timing.navigationStart&nbsp;+&nbsp;performance.now()
                                                                                                         
                                                                                                         // 示例
                                                                                                         const t1 = performance.timing.navigationStart + performance.now()
                                                                                                         const t2 = Date.now();
                                                                                                         console.log(t2, t1); 
                                                                                                         
                                                                                                         // t2 = 1686534658865 t1 = 1686534658865.2</code></pre><h4>时间精度降低</h4><p>为了提供对定时攻击和指纹的保护,<strong><code>performance.now()</code></strong> 的精度可能会根据浏览器的设置而被舍弃,在 <strong><code>Firefox</code></strong> 中,<strong><code>privacy.reduceTimerPrecision</code></strong> 偏好是默认启用的,默认值为 <strong><code>1ms</code></strong></p><pre><code class="js">// 降低时间精度 (1ms) 在 Firefox 60
                                                                                                         performance.now();
                                                                                                         // 8781416
                                                                                                         // 8781815
                                                                                                         // 8782206
                                                                                                         // ...
                                                                                                         
                                                                                                         // 降低时间精度 当 `privacy.resistFingerprinting` 启用
                                                                                                         performance.now();
                                                                                                         // 8865400
                                                                                                         // 8866200
                                                                                                         // 8866700
                                                                                                         // ...</code></pre><h4>到底该选谁?</h4><p>好家伙,说白了就还是没有一个完美的选择呗!</p><p></p><p>在这里选 <strong><code>Date.now()</code></strong>,毕竟 <strong><code>performance.now()</code></strong> 还得做转换,还有一个原因是 <a href="https://link.segmentfault.com/?enc=k%2FgDgIEKvDzJBxMzFEu8Jg%3D%3D.VHuerA5DhugfdPforP9o7JAcOdq5CFfL6B9cbFrrquUxV0kGj1dBcsmRAWn2c1CC7Un0g7WAe4VzP%2Ftp9kroWg%3D%3D" rel="nofollow" target="_blank"><strong>vant-count-down</strong></a> 组件也是用的 <strong><code>Date.now()</code></strong><del><strong><code>借鉴借鉴</code></strong></del>)。</p><h2 id="item-0-10">实现 CountDown 计时器组件</h2><h3 id="item-0-11">组件输入 — Props</h3><p>针对一个 <strong>CountDown 计时器组件</strong><strong>props</strong> 应该要包含如下几个内容:</p><ul><li><strong><code>time</code></strong>,即需要倒计时的时间</li><li><strong><code>format</code></strong>,即输出的时间格式,支持 <strong>DD:HH:mm:ss:SSS</strong> 格式</li><li><strong><code>finish</code> 事件</strong>,即倒计时结束时会被执行的事件</li><li><strong><code>slot</code> 默认插槽</strong>,即需要展示的组件内容视图,可接收到内部的倒计时格式输出</li></ul><p>其中时间我们可以直接限制为 <strong>时间戳</strong>,数值类型,当然如果你想支持更多格式,可以自己在写一个方法处理允许外部传入的各种格式,但实际在组件内部使用时必定是保持是同一种类型,因此在这里我们直接限定类型,让外部去进行转换。</p><h3 id="item-0-12">组件输出</h3><p>由于是一个基本的 <strong>CountDown 计时器组件</strong>,我们可以不考虑那么多输出,但至少要向外部暴露如下两个内容:</p><ul><li><strong><code>start()</code></strong> 方法,便于使用时可以基于任意时间开始进行倒计时</li><li><p><strong>格式化的倒计时</strong>,便于外部直接用于展示处理,或自定义展示,返回格式如下</p><pre><code class="js">   { 
                                                                                                                format, // 对应格式化的结果
                                                                                                                       days, // 天数
                                                                                                                              hours, // 小时
                                                                                                                                     minutes, //分钟
                                                                                                                                            seconds, // 秒数
                                                                                                                                                   milliseconds, // 毫秒
                                                                                                                                                      }</code></pre></li></ul><h3 id="item-0-13">具体实现</h3><h4>基本思路</h4><ul><li>根据 <strong>传入时间 <code>time</code></strong> 派生出 <strong>剩余时间 <code>remain</code></strong>,并计算出对应的 <strong>结束时间 <code>endTime</code></strong></li><li>通过 <strong>requestAnimationFrame + 递归</strong> 的方式更新 <strong><code>remain</code></strong> 值,即 <strong><code>remain = endTime - Date.now()</code></strong></li><li><p>根据最新的 <strong><code>remain</code></strong> 值,通过 <strong><code>parseTime()</code></strong><strong><code>formatTime()</code></strong> 方法进行转换返回对应的结果</p><ul><li><strong><code>parseTime()</code></strong> 负责将 <strong><code>remain</code></strong> 值转换成 <strong>天数/小时/分钟/秒/毫秒</strong> 等值</li><li><strong><code>formatTime()</code></strong> 负责将输出结果格式化,例如 <strong>不足位补 0</strong></li></ul></li></ul><h4>效果展示</h4><p></p><h4>具体代码</h4><h5>src\components\CountDown\index.vue</h5><pre><code class="js">&lt;template&gt;
                                                                                                                                                       &lt;div class="count-down"&gt;
                                                                                                                                                          &lt;slot v-bind="currentTime"&gt;
                                                                                                                                                               &lt;h1&gt;{{ currentTime.format }}&lt;/h1&gt;
                                                                                                                                                                  &lt;/slot&gt;
                                                                                                                                                                   &lt;/div&gt;
                                                                                                                                                                   &lt;/template&gt;
                                                                                                                                                                   
                                                                                                                                                                   &lt;script setup&gt;
                                                                                                                                                                   import { computed, ref, onMounted } from 'vue'
                                                                                                                                                                   import useCountDown from './Composable/useCountDown'
                                                                                                                                                                   
                                                                                                                                                                   const props = defineProps({
                                                                                                                                                                    time: {
                                                                                                                                                                       type: Number,
                                                                                                                                                                          default: 0,
                                                                                                                                                                           },
                                                                                                                                                                            format: {
                                                                                                                                                                               type: String,
                                                                                                                                                                                  default: 'DD:HH:mm:ss:SSS',
                                                                                                                                                                                   },
                                                                                                                                                                                    immediate: {
                                                                                                                                                                                       type: Boolean,
                                                                                                                                                                                          default: true,
                                                                                                                                                                                           },
                                                                                                                                                                                           })
                                                                                                                                                                                           
                                                                                                                                                                                           const emits = defineEmits(['finish'])
                                                                                                                                                                                           
                                                                                                                                                                                           const { start, currentTime } = useCountDown({
                                                                                                                                                                                            ...props,
                                                                                                                                                                                             onFinish: () =&gt; emits('finish'),
                                                                                                                                                                                             })
                                                                                                                                                                                             
                                                                                                                                                                                             // 判断是否需要立即执行
                                                                                                                                                                                             onMounted(() =&gt; {
                                                                                                                                                                                              if (props.immediate) start()
                                                                                                                                                                                              })
                                                                                                                                                                                              
                                                                                                                                                                                              // 向外部暴露的内容
                                                                                                                                                                                              defineExpose({
                                                                                                                                                                                               start,
                                                                                                                                                                                                currentTime,
                                                                                                                                                                                                })
                                                                                                                                                                                                &lt;/script&gt;</code></pre><h5>src\components\CountDown\composable\useCountDown\index.ts</h5><pre><code class="js">import { computed, ref } from 'vue'
                                                                                                                                                                                                import { parseTime, formatTime } from '../../utils'
                                                                                                                                                                                                
                                                                                                                                                                                                
                                                                                                                                                                                                export default (options) =&gt; {
                                                                                                                                                                                                    // 是否正在倒计时
                                                                                                                                                                                                        let counting = false
                                                                                                                                                                                                        
                                                                                                                                                                                                            // 剩余时间
                                                                                                                                                                                                                const remain = ref(options.time)
                                                                                                                                                                                                                
                                                                                                                                                                                                                    // 结束时间
                                                                                                                                                                                                                        const endTime = ref(0)
                                                                                                                                                                                                                        
                                                                                                                                                                                                                            // 格式化输出的日期时间
                                                                                                                                                                                                                                const currentTime = computed(() =&gt; formatTime(options.format, parseTime(remain.value)))
                                                                                                                                                                                                                                
                                                                                                                                                                                                                                    // 获取当前剩余时间
                                                                                                                                                                                                                                        const getCurrentRemain = () =&gt; Math.max(endTime.value - Date.now(), 0)
                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                            // 设置剩余时间
                                                                                                                                                                                                                                                const setRemain = (value) =&gt; {
                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                        // 更新剩余时间
                                                                                                                                                                                                                                                                remain.value = value
                                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                        // 倒计时结束
                                                                                                                                                                                                                                                                                if (value === 0) {
                                                                                                                                                                                                                                                                                            // 触发 Finish 事件
                                                                                                                                                                                                                                                                                                        options.onFinish?.()
                                                                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                    // 正在倒计时标志为 false
                                                                                                                                                                                                                                                                                                                                counting = false
                                                                                                                                                                                                                                                                                                                                        }
                                                                                                                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                // 倒计时
                                                                                                                                                                                                                                                                                                                                                    const tickTime = () =&gt; {
                                                                                                                                                                                                                                                                                                                                                            requestAnimationFrame(() =&gt; {
                                                                                                                                                                                                                                                                                                                                                                        // 更新剩余时间
                                                                                                                                                                                                                                                                                                                                                                                    setRemain(getCurrentRemain())
                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                // 倒计时没结束,就继续
                                                                                                                                                                                                                                                                                                                                                                                                            if (remain.value &gt; 0) {
                                                                                                                                                                                                                                                                                                                                                                                                                            tickTime()
                                                                                                                                                                                                                                                                                                                                                                                                                                        }
                                                                                                                                                                                                                                                                                                                                                                                                                                                })
                                                                                                                                                                                                                                                                                                                                                                                                                                                    }
                                                                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                        // 启动
                                                                                                                                                                                                                                                                                                                                                                                                                                                            const start = () =&gt; {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                    // 正在倒计时,忽略多次调用 start 
                                                                                                                                                                                                                                                                                                                                                                                                                                                                            if (counting) return
                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    // 正在倒计时标志为 true
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            counting = true
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    // 设置结束时间
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            endTime.value = Date.now() + remain.value
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    // 开启倒计时
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            tickTime()
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    return {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            currentTime,
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    start
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        }</code></pre><h5>src\components\CountDown\utils\index.ts</h5><pre><code class="js">// 常量
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        const SECOND = 1000
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        const MINUTE = 60 * SECOND
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        const HOUR = 60 * MINUTE
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        const DAY = 24 * HOUR
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        // 解析时间
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        export const parseTime = (time) =&gt; {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          const days = Math.floor(time / DAY)
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            const hours = Math.floor((time % DAY) / HOUR)
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              const minutes = Math.floor((time % HOUR) / MINUTE)
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                const seconds = Math.floor((time % MINUTE) / SECOND)
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  const milliseconds = Math.floor(time % SECOND)
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    return {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        days,
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            hours,
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                minutes,
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    seconds,
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        milliseconds,
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          // 格式化时间
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          export const formatTime = (format, time) =&gt; {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            let { days, hours, minutes, seconds, milliseconds } = time
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              // 判断是否需要展示 天数,需要则补 0,否则将 天数 降级加到 小时 部分
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                if (format.includes('DD')) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    format = format.replace('DD', padZero(days))
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      } else {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          hours += days * 24
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              // 判断是否需要展示 小时,需要则补 0,否则将 小时 降级加到 分钟 部分
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                if (format.includes('HH')) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    format = format.replace('HH', padZero(hours))
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      } else {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          minutes += hours * 60
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              // 判断是否需要展示 分钟,需要则补 0,否则将 分钟 降级加到 秒数 部分
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                if (format.includes('mm')) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    format = format.replace('mm', padZero(minutes))
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      } else {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          seconds += minutes * 60
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              // 判断是否需要展示 秒数,需要则补 0,否则将 秒数 降级加到 毫秒 部分
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                if (format.includes('ss')) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    format = format.replace('ss', padZero(seconds))
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      } else {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          milliseconds += seconds * 1000
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              // 默认展示 3位 毫秒数
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                if (format.includes('SSS')) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    const ms = padZero(milliseconds, 3)
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        format = format.replace('SSS', ms)
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            // 最终返回格式化的数据
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              return { format, days, hours, minutes, seconds, milliseconds }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              // 不足位数用 0 填充
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              export const padZero = (str, padLength = 2) =&gt; {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                str += ''
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  if (str.length &lt; padLength) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      str = '0'.repeat(padLength - str.length) + str
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          return str
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          }</code></pre><h2 id="item-0-14">最后</h2><blockquote><strong>欢迎关注同名公众号《<code>熊的猫</code>》,文章会同步更新,也可快速加入前端交流群!</strong></blockquote><p>以上就是一个基本的计时器的实现了,其中肯定有不足之处,不过大家只需要抓住核心思想即可,很多内容都借鉴了 <a href="https://link.segmentfault.com/?enc=bVvB3b%2BDF%2FXUY%2FSX52PAUg%3D%3D.0g46ouJ1Z7%2BCP%2BcqOu2qmoDqLzy3Id804CFny%2Fq2z1Urupi3kmCDQ6pZC0fX9tgHS6aHXyW5iLkm0%2FyZ3bnwmg%3D%3D" rel="nofollow" target="_blank"><strong>vant-count-down 组件</strong></a> 的实现,感兴趣可以直接去看其对应的源码。</p><p><strong>希望本文对你有所帮助!!!</strong></p><p></p>