javascript计时器_JavaScript计时器:您需要了解的一切

javascript计时器

by Samer Buna

通过Samer Buna

JavaScript计时器:您需要了解的一切 (JavaScript Timers: Everything you need to know)

A few weeks ago, I tweeted this interview question:

几周前,我在推特上发布了这个面试问题:

*** Answer the question in your head now before you proceed ***

***在继续之前,请先在脑海中回答问题***

About half the replies to the Tweet were wrong. The answer is NOT V8 (or other VMs)!! While famously known as “JavaScript Timers”, functions like setTimeout and setInterval are not part of the ECMAScript specs or any JavaScript engine implementations. Timer functions are implemented by browsers and their implementations will be different among different browsers. Timers are also implemented natively by the Node.js runtime itself.

大约有一半的回复是错误的。 答案不是 V8(或其他VM)! 尽管以“ JavaScript计时器”而闻名,但setTimeoutsetInterval之类的功能并不是ECMAScript规范或任何JavaScript引擎实现的一部分。 计时器功能由浏览器实现,不同浏览器的实现方式会有所不同。 计时器也由Node.js运行时本身本地实现。

In browsers, the main timer functions are part of the Window interface, which has a few other functions and objects. That interface makes all of its elements available globally in the main JavaScript scope. This is why you can execute setTimeout directly in your browser’s console.

在浏览器中,主要的计时器功能是Window界面的一部分,它具有一些其他功能和对象。 该接口使其所有元素在主要JavaScript范围内全局可用。 这就是为什么您可以直接在浏览器的控制台中执行setTimeout原因。

In Node, timers are part of the global object, which behaves similarly to the browser’s Window interface. You can see the source code of timers in Node here.

在Node中,计时器是global对象的一部分,其行为类似于浏览器的Window界面。 您可以在Node 此处查看计时器的源代码。

Some might think this is a bad interview question — why does knowing this matter anyway?! As a JavaScript developer, I think you’re expected to know this because if you don’t, that might be a sign that you don’t completely understand how V8 (and other VMs) interacts with browsers and Node.

有人可能认为这是一个不好的面试问题-为什么仍然知道这个问题呢? 作为JavaScript开发人员,我认为您应该知道这一点,因为如果您不了解这一点,则可能表明您不完全了解V8(和其他VM)如何与浏览器和Node交互。

Let’s do a few examples and challenges about timer functions, shall we?

让我们做一些有关计时器功能的例子和挑战,对吧?

Update: This article is now part of my “Complete Introduction to Node.js”.

更新:本文现在是我的“ Node.js完整介绍”的一部分。

You can read the updated version of it at here.

您可以在此处阅读其更新版本。

延迟执行功能 (Delaying the execution of a function)

Timer functions are higher-order functions that can be used to delay or repeat the execution of other functions (which they receive as their first argument).

定时器函数是高阶函数,可用于延迟或重复其他函数的执行(它们作为第一个参数接收)。

Here’s an example about delaying:

这是有关延迟的示例:

// example1.js
setTimeout(
  () => {
    console.log('Hello after 4 seconds');
  },
  4 * 1000
);

This example uses setTimeout to delay the printing of the greeting message by 4 seconds. The second argument to setTimeout is the delay (in ms). This is why I multiplied 4 by 1000 to make it into 4 seconds.

本示例使用setTimeout将问候消息的打印延迟4秒。 setTimeout的第二个参数是延迟(以毫秒为单位)。 这就是为什么我将4乘以1000使其变为4秒的原因。

The first argument to setTimeout is the function whose execution will be delayed.

setTimeout的第一个参数是将延迟执行的函数。

If you execute the example1.js file with the node command, Node will pause for 4 seconds and then it’ll print the greeting message (and exit after that).

如果使用node命令执行example1.js文件,Node将暂停4秒钟,然后将打印问候消息(此后退出)。

Note that the first argument to setTimeout is just a function reference. It does not have to be an inline function like what example1.js has. Here’s the same example without using an inline function:

请注意, setTimeout的第一个参数只是一个函数引用 。 它不必像example1.js那样是内联函数。 这是不使用内联函数的相同示例:

const func = () => {
  console.log('Hello after 4 seconds');
};
setTimeout(func, 4 * 1000);

传递参数 (Passing Arguments)

If the function that uses setTimeout to delay its execution accepts any arguments, we can use the remaining arguments for setTimeout itself (after the 2 we learned about so far) to relay the argument values to the delayed function.

如果使用setTimeout延迟执行的函数接受任何参数,则可以使用setTimeout本身的其余参数(到目前为止,我们已经学习了2个参数)将参数值传递给延迟的函数。

// For: func(arg1, arg2, arg3, ...)
// We can use: setTimeout(func, delay, arg1, arg2, arg3, ...)

Here’s an example:

这是一个例子:

// example2.js
const rocks = who => {
  console.log(who + ' rocks');
};
setTimeout(rocks, 2 * 1000, 'Node.js');

The rocks function above, which is delayed by 2 seconds, accepts a who argument and the setTimeout call relays the value “Node.js” as that who argument.

上面的rocks函数(延迟2秒)接受一个who参数,而setTimeout调用将值“ Node.js ”中继为who参数。

Executing example2.js with the node command will print out “Node.js rocks” after 2 seconds.

2秒钟后,使用node命令执行example2.js将打印出“ Node.js rock ”。

计时器挑战1 (Timers Challenge 1)

Using what you learned so far about setTimeout, print the following 2 messages after their corresponding delays.

使用到目前为止所学的有关setTimeout ,在相应的延迟后打印以下2条消息。

  • Print the message “Hello after 4 seconds” after 4 seconds

    4秒后打印消息“ 4秒您好

  • Print the message “Hello after 8 seconds” after 8 seconds.

    8秒后打印消息“ 8秒您好 ”。

Constraints:You can define only a single function in your solution, which includes inline functions. This means many setTimeout calls will have to use the exact same function.

约束 :您只能在解决方案中定义一个函数,其中包括内联函数。 这意味着许多setTimeout调用将必须使用完全相同的函数。

(Solution)

Here’s how I’d solve this challenge:

这是我要解决的挑战的方法:

// solution1.js
const theOneFunc = delay => {
  console.log('Hello after ' + delay + ' seconds');
};
setTimeout(theOneFunc, 4 * 1000, 4);
setTimeout(theOneFunc, 8 * 1000, 8);

I’ve made theOneFunc receive a delay argument and used the value of that delay argument in the printed message. This way, the function can print different messages based on whatever delay value we pass to it.

我已经使theOneFunc接收了一个delay参数,并在打印的消息中使用了该delay参数的值。 这样,该函数可以根据传递给它的延迟值来打印不同的消息。

I then used theOneFunc in two setTimeout calls, one that fires after 4 seconds and another that fires after 8 seconds. Both of these setTimeout calls also get a 3rd argument to represent the delay argument for theOneFunc.

然后,我在两个setTimeout调用中使用theOneFunc ,一个在4秒后触发,另一个在8秒后触发。 这两个setTimeout调用还都获得了第3个参数来表示theOneFuncdelay参数。

Executing the solution1.js file with the node command will print out the challenge requirements, the first message after 4 seconds, and the second message after 8 seconds.

使用node命令执行solution1.js文件将打印出质询要求,即4秒钟后的第一条消息和8秒钟后的第二条消息。

重复执行功能 (Repeating the execution of a function)

What if I asked you to print a message every 4 seconds, forever?

如果我要求您每4秒钟永久打印一条消息怎么办?

While you can put setTimeout in a loop, the timers API offers the setInterval function as well, which would accomplish the requirement of doing something forever.

尽管您可以将setTimeout放入循环中,但是timers API还提供了setInterval函数,该函数可以永远完成操作。

Here’s an example of setInterval:

这是setInterval的示例:

// example3.js
setInterval(
  () => console.log('Hello every 3 seconds'),
  3000
);

This example will print its message every 3 seconds. Executing example3.js with the node command will make Node print this message forever, until you kill the process (with CTRL+C).

本示例将每3秒打印一次其消息。 使用node命令执行example3.js将使Node永远打印此消息,直到您终止进程(使用CTRL + C )。

取消计时器 (Cancelling Timers)

Because calling a timer function schedules an action, that action can also be cancelled before it gets executed.

因为调用计时器函数会计划一个动作,所以该动作也可以在执行之前取消。

A call to setTimeout returns a timer “ID” and you can use that timer ID with a clearTimeout call to cancel that timer. Here’s an example:

调用setTimeout返回一个计时器“ ID”,您可以将该计时器ID与clearTimeout调用一起使用以取消该计时器。 这是一个例子:

// example4.js
const timerId = setTimeout(
  () => console.log('You will not see this one!'),
  0
);
clearTimeout(timerId);

This simple timer is supposed to fire after 0 ms (making it immediate), but it will not because we are capturing the timerId value and canceling it right after with a clearTimeout call.

这个简单的计时器应该在0毫秒后触发(使其立即生效),但不timerId因为我们正在捕获timerId值并在之后通过clearTimeout调用将其取消。

When we execute example4.js with the node command, Node will not print anything and the process will just exit.

当我们使用node命令执行example4.js时,Node将不打印任何内容,并且该过程将退出。

By the way, in Node.js, there is another way to do setTimeout with 0 ms. The Node.js timer API has another function called setImmediate, and it’s basically the same thing as a setTimeout with a 0 ms but we don’t have to specify a delay there:

顺便说一下,在Node.js中,还有另一种方法可以将setTimeout0 ms。 Node.js计时器API还有另一个名为setImmediate函数,它与具有0毫秒的setTimeout基本相同,但是我们不必在那里指定延迟:

setImmediate(
  () => console.log('I am equivalent to setTimeout with 0 ms'),
);

The setImmediate function is not available in all browsers. Don’t use it for front-end code.

setImmediate函数并非在所有浏览器中都可用 不要将其用于前端代码。

Just like clearTimeout, there is also a clearInterval function, which does the same thing but for setInerval calls, and there is also a clearImmediate call as well.

就像clearTimeout一样,还有一个clearInterval函数,除了setInerval调用外,它的功能相同,并且还有一个clearImmediate调用。

计时器延迟不是保证的事情 (A timer delay is not a guaranteed thing)

In the previous example, did you notice how executing something with setTimeout after 0 ms did not mean execute it right away (after the setTimeout line), but rather execute it right away after everything else in the script (including the clearTimeout call)?

在上一个示例中,您是否注意到在0毫秒后用setTimeout执行某件事并不意味着立即执行(在setTimeout行之后),而是立即在脚本中的所有其他内容(包括clearTimeout调用)之后立即执行?

Let me make this point clear with an example. Here’s a simple setTimeout call that should fire after half a second, but it won’t:

让我通过一个例子阐明这一点。 这是一个简单的setTimeout调用,应在半秒钟后触发,但不会:

// example5.js
setTimeout(
  () => console.log('Hello after 0.5 seconds. MAYBE!'),
  500,
);
for (let i = 0; i < 1e10; i++) {
  // Block Things Synchronously
}

Right after defining the timer in this example, we block the runtime synchronously with a big for loop. The 1e10 is 1 with 10 zeros in front of it, so the loop is a 10 Billion ticks loop (which basically simulates a busy CPU). Node can do nothing while this loop is ticking.

在此示例中定义了计时器之后,我们立即通过一个大的for循环来阻塞运行时。 1e101 ,前面有10零,因此该循环是一个10亿滴答循环(基本上模拟了繁忙的CPU)。 循环滴答作响时,节点无法执行任何操作。

This of course is a very bad thing to do in practice, but it’ll help you here to understand that setTimeout delay is not a guaranteed thing, but rather a minimum thing. The 500 ms means a minimum delay of 500 ms. In reality, the script will take a lot longer to print its greeting line. It will have to wait on the blocking loop to finish first.

在实践中,这当然是一件非常糟糕的事情,但是它将帮助您在这里了解setTimeout延迟不是保证的事情,而是最小的事情。 500 ms表示最小延迟为500 ms。 实际上,该脚本将花费更长的时间来打印其问候语。 它必须等待阻塞循环才能首先完成。

计时器挑战2 (Timers Challenge 2)

Write a script to print the message “Hello World” every second, but only 5 times. After 5 times, the script should print the message “Done” and let the Node process exit.

编写脚本以每秒打印一次消息“ Hello World ”,但仅打印5次。 5次后,脚本应打印消息“ Done ”,并退出Node进程。

Constraints: You cannot use a setTimeout call for this challenge. Hint: You need a counter.

约束 :您不能将setTimeout调用用于此挑战。 提示 :您需要一个柜台。

(Solution)

Here’s how I’d solve this one:

这是我要解决的方法:

let counter = 0;
const intervalId = setInterval(() => {
  console.log('Hello World');
  counter += 1;
if (counter === 5) {
    console.log('Done');
    clearInterval(intervalId);
  }
}, 1000);

I initiated a counter value as 0 and then started a setInterval call capturing its id.

我将counter值初始化为0 ,然后启动了setInterval调用以捕获其ID。

The delayed function will print the message and increment the counter each time. Inside the delayed function, an if statement will check if we’re at 5 times by now. If so, it’ll print “Done” and clear the interval using the captured intervalId constant. The interval delay is 1000 ms.

延迟功能将打印消息并每次增加计数器。 在延迟函数内部,一条if语句将检查我们现在是否处于5倍。 如果是这样,它将打印“ Done ”并使用捕获的intervalId常数清除intervalId 。 间隔延迟为1000毫秒。

谁确切地“调用”了延迟功能? (Who exactly “calls” the delayed functions?)

When you use the JavaScript this keyword inside a regular function, like this:

当您使用JavaScript时,在常规函数中使用this关键字,如下所示:

function whoCalledMe() {
  console.log('Caller is', this);
}

The value inside the this keyword will represent the caller of the function. If you define the function above inside a Node REPL, the caller will be the global object. If you define the function inside a browser’s console, the caller will be the window object.

this关键字内的值将代表函数的调用者 。 如果您在Node REPL内定义以上函数,则调用者将是global对象。 如果在浏览器的控制台中定义函数,则调用者将是window对象。

Let’s define the function as a property on an object to make this a bit more clear:

让我们将函数定义为对象的属性,以使其更加清楚:

const obj = { 
  id: '42',
  whoCalledMe() {
    console.log('Caller is', this);
  }
};
// The function reference is now: obj.whoCallMe

Now when you call the obj.whoCallMe function using its reference directly, the caller will be the obj object (identified by its id):

现在,当您直接使用其引用调用obj.whoCallMe函数时,调用者将是obj对象(由其ID标识):

Now, the question is, what would the caller be if we pass the reference of obj.whoCallMe to a setTimetout call?

现在的问题是,如果将obj.whoCallMe的引用obj.whoCallMesetTimetout调用,调用者将是什么?

// What will this print??
setTimeout(obj.whoCalledMe, 0);

Who will the caller be in that case?

在这种情况下,呼叫者将是谁?

The answer is different based on where the timer function is executed. You simply can’t depend on who the caller is in that case. You lose control of the caller because the timer implementation will be the one invoking your function now. If you test it in a Node REPL, you’d get a Timetout object as the caller:

答案因在何处执行计时器功能而异。 在这种情况下,您根本无法确定呼叫者是谁。 您失去了对调用者的控制,因为计时器实现将成为现在调用您函数的功能。 如果在Node REPL中对其进行测试,您将获得一个Timetout对象作为调用者:

Note that this only matters if you’re using JavaScript’s this keyword inside regular functions. You don’t need to worry about the caller at all if you’re using arrow functions.

请注意,这仅在常规函数中使用JavaScript的this关键字时才重要。 如果使用箭头功能,则完全不必担心调用方。

计时器挑战3 (Timers Challenge 3)

Write a script to continuously print the message “Hello World” with varying delays. Start with a delay of 1 second and then increment the delay by 1 second each time. The second time will have a delay of 2 seconds. The third time will have a delay of 3 seconds, and so on.

编写脚本以不断变化的延迟连续打印消息“ Hello World ”。 从1秒的延迟开始,然后每次将延迟增加1秒。 第二次会有2秒的延迟。 第三时间会有3秒的延迟,依此类推。

Include the delay in the printed message. Expected output looks like:

在打印的消息中包括延迟。 预期输出如下:

Hello World. 1
Hello World. 2
Hello World. 3
...

Constraints: You can only use const to define variables. You can’t use let or var.

约束:您只能使用const定义变量。 您不能使用letvar

(Solution)

Because the delay amount is a variable in this challenge, we can’t use setInterval here, but we can manually create an interval execution using setTimeout within a recursive call. The first executed function with setTimeout will create another timer, and so on.

由于延迟量是此挑战中的变量,因此我们不能在此处使用setInterval ,但可以在递归调用中使用setTimeout手动创建间隔执行。 第一个使用setTimeout执行的函数将创建另一个计时器,依此类推。

Also, because we can’t use let/var, we can’t have a counter to increment the delay in each recursive call, but we can instead use the recursive function arguments to increment during the recursive call.

另外,因为我们不能使用let / var,所以不能有一个计数器来增加每次递归调用中的延迟,但是我们可以使用递归函数参数在递归调用中递增。

Here’s one possible way to solve this challenge:

这是解决此难题的一种可能方法:

const greeting = delay =>
  setTimeout(() => {
    console.log('Hello World. ' + delay);
    greeting(delay + 1);
  }, delay * 1000);
greeting(1);

计时器挑战4 (Timers Challenge 4)

Write a script to continuously print the message “Hello World” with the same varying delays concept as challenge #3, but this time, in groups of 5 messages per main-delay interval. Starting with a delay of 100ms for the first 5 messages, then a delay of 200ms for the next 5 messages, then 300ms, and so on.

编写脚本,以与挑战#3相同的可变延迟概念连续打印消息“ Hello World ”,但是这次,每个主延迟间隔以5条消息为一组。 从前5条消息的100ms延迟开始,然后是下5条消息的200ms延迟,然后是300ms,依此类推。

Here’s how the script should behave:

脚本的行为如下:

  • At the 100ms point, the script will start printing “Hello World” and do that 5 times with an interval of 100ms. The 1st message will appear at 100ms, 2nd message at 200ms, and so on.

    在100毫秒时,脚本将开始打印“ Hello World”,并以100毫秒的间隔执行5次。 第一条消息将在100毫秒处显示,第二条消息将在200毫秒处出现,依此类推。
  • After the first 5 messages, the script should increment the main delay to 200ms. So 6th message will be printed at 500ms + 200ms (700ms), 7th message will be printed at 900ms, 8th message will be printed at 1100ms, and so on.

    在前5条消息之后,脚本应将主延迟增加到200ms。 因此,第6条消息将以500ms + 200ms(700ms)打印,第7条消息将以900ms打印,第8条消息将以1100ms打印,依此类推。
  • After 10 messages, the script should increment the main delay to 300ms. So the 11th message should be printed at 500ms + 1000ms + 300ms (18000ms). The 12th message should be printed at 21000ms, and so on.

    10条消息后,脚本应将主延迟增加到300ms。 因此,第11条消息应以500ms + 1000ms + 300ms(18000ms)打印。 第12条消息应在21000ms处打印,依此类推。
  • Continue the pattern forever.

    永远继续模式。

Include the delay in the printed message. The expected output looks like this (without the comments):

在打印的消息中包括延迟。 预期的输出如下所示(不带注释):

Hello World. 100  // At 100ms
Hello World. 100  // At 200ms
Hello World. 100  // At 300ms
Hello World. 100  // At 400ms
Hello World. 100  // At 500ms
Hello World. 200  // At 700ms
Hello World. 200  // At 900ms
Hello World. 200  // At 1100ms
...

Constraints: You can use only setInterval calls (not setTimeout) and you can use only ONE if statement.

约束:您只能使用setInterval调用(不能使用setTimeout ),并且只能使用一个if语句。

(Solution)

Because we can only use setInterval calls, we’ll need recursion here as well to increment the delay of the next setInterval call. In addition, we need an if statement to control doing that only after 5 calls of that recursive function.

因为我们只能使用setInterval调用,所以我们在这里也需要递归,以增加下一个setInterval调用的延迟。 另外,我们需要一个if语句来控制仅在对该递归函数的5次调用之后执行该操作。

Here’s one possible solution:

这是一种可能的解决方案:

let lastIntervalId, counter = 5;
const greeting = delay => {
  if (counter === 5) {
    clearInterval(lastIntervalId);
    lastIntervalId = setInterval(() => {
      console.log('Hello World. ', delay);
      greeting(delay + 100);
    }, delay);
    counter = 0;
  }
counter += 1;
};
greeting(100);

Thanks for reading.

谢谢阅读。

If you’re just beginning to learn Node.js, I recently published a first-steps course at Pluralsight, check it out:

如果您刚刚开始学习Node.js,我最近在Pluralsight上发布了第一步课程 ,请查看:

翻译自: https://www.freecodecamp.org/news/javascript-timers-everything-you-need-to-know-5f31eaa37162/

javascript计时器

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值