vanilla js_使用Vanilla JS创建精确的倒计时

vanilla js

On a recent technical interview I had for a big tech company, in one of the steps of the process I was asked the following:

在最近的一次技术采访中,我曾在一家大型高科技公司工作,在该过程的一个步骤中,我被询问以下内容:

Create a countdown from 1:30 to zero using plain javascript and HTML, and that’s all.

使用普通的javascript和HTML创建从1:30到0的倒计时,仅此而已。

“Cool”, I said to myself, that’s something I can achieve easily. The total time I had to develop that was 45 minutes, which at first looked more than enough.

我对自己说:“很酷”,这是我可以轻松实现的。 我必须开发的总时间为45分钟,起初看起来绰绰有余。

I started doing the simplest thing I could imagine, mimicking what a normal clock or countdown does: at each 1-second interval, we would decrease the value of the counter by 1 until the end of the 90-second period. This was the result:

我开始做我想像的最简单的事情,模仿正常的时钟或倒数计时:每隔1秒间隔,我们将计数器的值减1,直到90秒周期结束。 结果是:

const initialSeconds = 90;let remainingSeconds;/*** Returns seconds in format mm:ss* @param {Number} seconds* @return {string}*/const formatMinutesSeconds = (seconds) => {    const thisDate = new Date(seconds * 1000);
return `${thisDate.getMinutes()}:${thisDate.getSeconds()}`;};/*** Renders the remaining time in a format mm:ss* @param {Number} seconds*/const renderCountdown = seconds => { const counterElement = document.getElementById('counter'); const stringCounter = formatMinutesSeconds(seconds); counterElement.innerText = stringCounter;};/*** Starts a countdown with the given seconds* @param {Number} seconds*/const startCountdown = (seconds) => { remainingSeconds = seconds; setInterval(_ => {
if (remainingSeconds > 0) { remainingSeconds--; renderCountdown(remainingSeconds);
} }, 1000); renderCountdown(remainingSeconds);};startCountdown(initialSeconds);

As you can see, I used the native setInterval function to mimic a tick on a regular clock, and decreased the total amount of seconds by 1. By that time, around 10–15 minutes had passed and the interviewer told me:

如您所见,我使用本地的setInterval函数模仿常规时钟上的滴答声,并将总秒数减少了1。到那时,已经过去了大约10-15分钟,面试官告诉我:

Well done, but do you think it will be precise enough?

做得好,但是您认为它足够精确吗?

As you may know (or not), Javascript works with a single thread with no context switching, which in other words, means that functions are executed from the beginning until the end without interruptions, so, if there was another function somewhere taking a lot of time (let’s say a long for statement), it would cause some delays. Using a setInterval, will only guarantee that the callback function will be executed at least n seconds after the time is created, but if the Event Loop is busy at the moment of its completion, it will wait until it finishes all the previous tasks before running the code we want.

您可能知道(也可能不知道),JavaScript是在没有上下文切换的情况下使用单个线程工作的,换句话说,这意味着函数从头到尾执行而不会中断,因此,如果某个地方的另一个函数占用了很多资源,时间(比如说很长for发言时间)会导致一些延迟。 使用setInterval,只能保证创建时间后至少 n秒执行回调函数,但是如果事件循环在完成时很忙,它将等待直到完成所有之前的任务后再运行我们想要的代码。

If we want to be precise, this solution won’t work.

如果要精确,此解决方案将行不通。

I started thinking of possible variations to that.

我开始考虑可能的变化。

First, I thought of using a web worker, which will made the timer to run in the background and that may not solve it entirely, but that wasn’t the path the interviewer wanted me to take. He was interested on seeing how would I compare Date objects no matter when was the check being done, so, if the event loop happened to be locked for 5 seconds, the next “tick” will reduce the countdown by 5 seconds and it will be on sync.

首先,我想到了使用网络工作者,这会使计时器在后台运行,并且可能无法完全解决问题,但这不是采访者希望我采取的途径。 他很想知道无论检查何时完成,我将如何比较Date对象,因此,如果事件循环碰巧被锁定了5秒钟,则下一个“滴答”将使倒计时减少5秒钟,并且将同步中。

With the code I already had written, I needed to make a few changes to make it work like that. After struggling for some time, I came up with this solution:

使用我已经编写的代码,我需要进行一些更改以使其正常工作。 经过一段时间的努力,我想出了以下解决方案:

const initialSeconds = 90;/*** Returns seconds in format mm:ss* @param {Number} seconds*/const formatMinutesSeconds = (seconds) => {    const thisDate = new Date(seconds * 1000);    return `${thisDate.getMinutes()}:${thisDate.getSeconds()}`;};let remainingSeconds;const renderCountdown = seconds => {    const counterElement = document.getElementById('counter');    const stringCounter = formatMinutesSeconds(seconds);    counterElement.innerText = stringCounter;};const startCountdown = (seconds) => {    let initialTime = new Date();    setInterval(_ => {        const newTime = new Date();        const dateDiff = new Date(newTime - initialTime);        let secondsPassed = dateDiff.getSeconds();        if (secondsPassed > 0) {            renderCountdown(seconds - secondsPassed);        }    }, 100);    renderCountdown(seconds);}
startCountdown(initialSeconds);

The “magic” happens inside the callback function of setInterval. What I’m doing basically, is to store the Date object representing the time where the countdown started, and after 100 milliseconds, I’m checking if a second has passed (calculating the difference between the current time and the time the countdown started) and if that’s true, I will render the countdown with the new remaining seconds.

“魔术”发生在setInterval的回调函数内部。 我基本上要做的是存储代表倒数开始时间的Date对象,并且在100毫秒后,我正在检查是否经过了一秒(计算当前时间与倒数开始时间之间的差)如果是这样,我将使用剩余的新秒数来进行倒计时。

Well done, that looks good. To finish, now create a button to start and resume the countdown.

做得好,看起来不错。 要结束操作,现在创建一个按钮以开始和恢复倒计时。

By then, I didn’t have much time left, but I tried my best. What I did first, was creating a button to start/pause the countdown. That button would be in charge of creating the interval, and to clear it if it already existed.

到那时,我已经没有多少时间了,但是我尽了最大的努力。 我首先要做的是创建一个按钮来开始/暂停倒计时。 该按钮将负责创建间隔,并清除该间隔(如果已存在)。

The first blocker I found (that, in the end, was the one that caused my failure in finishing the task on time) was related to the initialTime variable I previously created. I encountered a few problems because when pausing the countdown and resuming it a few moments later, the value of seconds — secondsPassed was out of sync, because that intialTime was still the same. One of the solutions could have been to store the new initialTime after starting the countdown again, but that made no sense in my head, since the name of the variable made me think that I shouldn’t reassign its value.

我发现的第一个阻止程序(最后是导致我无法按时完成任务的initialTime )与我先前创建的initialTime变量有关。 我遇到了一些问题,因为在暂停倒计时并在稍后重新恢复时, seconds — secondsPassed的值seconds — secondsPassed不同步,因为intialTime仍然相同。 解决方案之一可能是在再次开始倒计时后存储新的initialTime ,但这对我来说毫无意义,因为变量的名称使我认为我不应该重新分配其值。

In the end, the interview finished and I couldn’t come up with a solution, but then with more time, I was able to make it work. Here is the final code:

最后,面试结束了,我无法提出解决方案,但是后来有了更多的时间,我得以使它可行。 这是最终代码:

const initialSeconds = 90;/*** Returns seconds in format mm:ss* @param {Number} seconds*/const formatMinutesSeconds = (seconds) => {    const thisDate = new Date(seconds * 1000);    return `${thisDate.getMinutes()}:${thisDate.getSeconds()}`;};const renderCountdown = seconds => {    const counterElement = document.getElementById('counter');    const stringCounter = formatMinutesSeconds(seconds);    counterElement.innerText = stringCounter;};window.addEventListener('DOMContentLoaded', () => {    const startButton = document.getElementById('btnStart');    let remainingSeconds = initialSeconds;    let intervalCountdown;
startButton.addEventListener('click', () => { startCountdown(remainingSeconds); });
renderCountdown(remainingSeconds);
const startCountdown = (seconds) => { let initialTime = new Date(); if (!intervalCountdown) { intervalCountdown = setInterval(_ => { const newTime = new Date(); const dateDiff = new Date(newTime - initialTime); let secondsPassed = dateDiff.getSeconds();
if (secondsPassed > 0) {
remainingSeconds = seconds - secondsPassed; renderCountdown(remainingSeconds); } }, 100); } else { clearInterval(intervalCountdown); intervalCountdown = null; } };});

As you can see, I’d store the remaining seconds in a variable and continue from there if the counter is resumed. It’s not as precise as it could be, but works.

如您所见,我将剩余的秒数存储在变量中,如果计数器继续运行,则从那里继续。 它没有尽可能精确,但是可以。

下一步 (Next steps)

  • Make it more accurate by storing the milliseconds passed, or even better, to update the Date object where the pause button was hit too.

    通过存储经过的毫秒数(甚至更好)来更新它,以更新也单击了暂停按钮的Date对象。
  • Improve the formatting of the timer, now if the seconds are less than 10, it will display something like 1:4 for 1 minute 4 seconds.

    改进计时器的格式,现在如果秒数少于10,它将在1分钟4秒内显示1:4

  • Clear the interval when it reaches 0 seconds and restart the counter.

    清除到达0秒的时间间隔,然后重新启动计数器。

结论 (Conclusion)

After I finished the exercise, I realised how simple it really was. The main problem I encountered was related to the refactor of what I wrote before. I didn’t want to change my initial design and struggled a lot to make it work with what I already had.

完成练习后,我意识到它真的很简单。 我遇到的主要问题与我之前写的内容的重构有关。 我不想更改我的初始设计,并且为了使其能够与已有的功能一起工作而费了很多力气。

Whenever I have to think to understand what the code is doing, I ask myself if I can refactor the code to make that understanding more immediately apparent.― Martin Fowler, Refactoring: Improving the Design of Existing Code

每当我不得不考虑理解代码在做什么时,我都会问自己是否可以重构代码,以使这种理解立即变得更加明显。—— Martin Fowler, 重构:改进现有代码的设计

What I should have done, was to read again the code I wrote and see what I really needed, and from there start all over again. Of course that when you have a clock running down telling you “you only have 10 minutes left” it makes things more difficult, but thankfully, I never faced that in real life and that’s not what I look for in a company I want to work for.

我应该做的是再次阅读我编写的代码,看看我真正需要什么,然后从头开始。 当然,当您的时钟快要告诉您“还剩10分钟”时,事情会变得更加困难,但是值得庆幸的是,我在现实生活中从未遇到过这种情况,而这并不是我想要工作的公司所追求的对于。

That experience was great though, and I learned a lot, the key is to have a clear mind and think before coding.

但是,这种经验很棒,而且我学到了很多东西,关键是在编码之前要有清晰的头脑和思考。

翻译自: https://medium.com/weekly-webtips/creating-a-precise-countdown-with-vanilla-js-cdc44c0483fa

vanilla js

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值