大家好 曾几何时,约翰·雷齐格(John Rezig)撰写了有关哈布雷(Habré)
的文章 。 十年过去了,这个话题仍然需要澄清。 因此,我们为那些有兴趣阅读Samer Buna的文章的人,该文章不仅提供了JavaScript中的计时器的理论概述(在Node.js的上下文中),而且还提供了有关它们的任务。

几周前,我在一次采访中发布了以下问题:
“ setTimeout和setInterval函数的源代码在哪里? 你要去哪里找他 您无法在Google上搜索它:)“
***自己回答,然后继续阅读***
对此推文的回复中约有一半是不正确的。 不,这种情况与V8(或其他VM)无关! 诸如
setTimeout
和
setInterval
类的功能(自豪地称为JavaScript JavaScript Timers)不属于任何ECMAScript规范或JavaScript引擎实现。 计时器功能是在浏览器级别实现的,因此它们的实现在不同的浏览器中会有所不同。 计时器也是在Node.js运行时本身中本地实现的。
在浏览器中,主要计时器功能是指
Window
界面,该界面也与其他一些功能和对象相关联。 该接口在JavaScript的主要范围内提供对其所有元素的全局访问。 这就是为什么可以在浏览器控制台中直接执行
setTimeout
函数的原因。
在Node中,计时器是
global
对象的一部分,该对象的设计类似于
Window
的浏览器界面。
这里显示了Node中计时器的源代码。
在某人看来,这只是面试中的一个坏问题-知道这一点有什么好处? 作为JavaScript开发人员,我这样想:假定您应该了解这一点,因为相反的情况可能表明您不太了解V8(和其他虚拟机)如何与浏览器和Node交互。
让我们看一些示例并解决几个计时器任务,我们来吗?
您可以使用node命令来运行本文中的示例。 我在Pluralsight 上的Node.js入门课程中介绍了此处讨论的大多数示例。延迟执行功能计时器是高阶函数,您可以使用它们延迟或重复执行其他功能(计时器接收的功能作为第一个参数)。
这是延迟执行的示例:
在此示例中,使用
setTimeout
将问候消息延迟4秒。
setTimeout
的第二个参数是延迟(以毫秒为单位)。 我将4乘以1000得到4秒。
setTimeout
的第一个参数是一个函数,其执行将被延迟。
如果使用node命令执行
example1.js
文件,则Node会暂停4秒钟,然后显示一条欢迎消息(后面是退出消息)。
请注意:
setTimeout
的第一个参数只是一个
函数引用 。 它不应是内置函数-例如
example1.js
。 这是不使用内置函数的相同示例:
const func = () => { console.log('Hello after 4 seconds'); }; setTimeout(func, 4 * 1000);
传递参数如果使用
setTimeout
延迟的函数接受任何参数,则可以使用
setTimeout
函数本身的其余参数(在我们已经研究过的2个参数之后)将参数的值传输到延迟函数。
这是一个例子:
上面的
rocks
函数延迟了2秒,它使用了
who
参数,并调用
setTimeout
将值“ Node.js”传递给了
who
参数。
当使用
node
命令执行
example2.js
时,将在2秒后显示短语“ Node.js rocks”。
计时器任务1因此,基于已经研究过的有关
setTimeout
,我们将在相应的延迟之后显示以下2条消息。
- 4秒后将显示消息“ 4秒后您好”。
- 8秒后将显示消息“ 8秒后您好”。
局限性在您的解决方案中,您只能定义一个包含内置函数的函数。 这意味着许多
setTimeout
调用将必须使用相同的函数。
解决方案这是我如何解决此问题的方法:
对我而言,
theOneFunc
接收
delay
参数,并在屏幕上显示的消息中使用此
delay
参数的值。 因此,该函数可以显示不同的消息,具体取决于我们将告知它的延迟值。
然后,我在两个
setTimeout
调用中使用了
theOneFunc
,第一个调用在4秒后触发,第二个在8秒后触发。 这两个
setTimeout
调用也都接收一个第3个参数,表示
theOneFunc
的
delay
参数。
通过使用node命令执行
solution1.js
文件,我们将显示任务的要求,第一条消息将在4秒后出现,第二条消息将在8秒后出现。
重复功能但是,如果我要您无限期每4秒显示一条消息怎么办?
当然,您可以
setTimeout
在一个循环中,但是计时器API还提供了
setInterval
函数,您可以使用该函数对任何操作的“永恒”执行进行编程。
这是
setInterval
的示例:
此代码将每3秒显示一条消息。 如果使用
node
命令执行
example3.js
,则Node将输出此命令,直到您强制结束该过程(CTRL + C)。
取消计时器由于在调用计时器函数时分配了一个动作,因此在执行该动作之前也可以将其撤消。
setTimeout
调用返回一个计时器ID,您可以在调用
clearTimeout
取消计时器时使用此计时器ID。 这是一个例子:
这个简单的计时器应该在0毫秒后(即立即)触发,但这不会发生,因为我们捕获了
timerId
的值,并通过调用
clearTimeout
立即取消了此计时器。
使用
node
命令执行
example4.js
时,Node将不会输出任何内容-该过程将立即结束。
顺便说一下,Node.js还提供了另一种
setTimeout
的方式,值为0 ms。 Node.js计时器API中还有另一个名为
setImmediate
,它的功能基本上与
setTimeout
相同,但值为0 ms,但是在这种情况下,您可以省略延迟:
setImmediate( () => console.log('I am equivalent to setTimeout with 0 ms'), );
setImmediate
所有浏览器都支持 setImmediate
函数。 不要在客户端代码中使用它。
除了
clearTimeout
还有一个
clearInterval
函数,它的功能相同,但是有
setInerval
调用,还有一个
clearImmediate
调用。
计时器延迟-无法保证您是否注意到在前面的示例中,使用
setTimeout
在0 ms之后执行操作时,此操作不会立即发生(在
setTimeout
之后),而是仅在所有脚本代码都已完全执行(包括
clearTimeout
调用)之后发生吗?
让我用一个例子阐明这一点。 这是一个简单的
setTimeout
调用,应该在半秒钟内起作用-但这不会发生:
在此示例中定义了计时器之后,我们立即使用大的
for
循环同步阻塞了运行时环境。
1e10
值为1,带有10个零,因此该周期持续100亿个处理器周期(原则上,这模拟了处理器过载)。 在此循环完成之前,节点无法执行任何操作。
当然,实际上这是非常糟糕的,但是此示例有助于理解
setTimeout
延迟并不能保证,而是可以保证
最小值 。 500 ms的值表示延迟将持续至少500 ms。 实际上,该脚本将需要更长的时间才能在屏幕上显示欢迎行。 首先,他将不得不等待直到阻塞周期完成。
计时器问题2编写一个脚本,该脚本每秒显示一次“ Hello World”消息,但仅显示5次。 经过5次迭代后,脚本应显示“ Done”消息,此后Node进程将完成。
限制 :解决此问题时,不能调用
setTimeout
。
提示 :需要柜台。
解决方案这是我如何解决此问题的方法:
let counter = 0; const intervalId = setInterval(() => { console.log('Hello World'); counter += 1; if (counter === 5) { console.log('Done'); clearInterval(intervalId); } }, 1000);
我将0设置为
counter
的初始值,然后调用
setInterval
,它获取其ID。
延迟功能将显示一条消息,并且每次将计数器增加一。 在deferred函数内部,我们有一个if语句,它将检查是否已经经过5次迭代。 5次迭代后,程序将显示“ Done”,并使用捕获的
intervalId
常数清除间隔值。 间隔延迟为1000毫秒。
谁确切地称为延迟函数?使用JavaScript时,在常规函数中使用
this
,例如:
function whoCalledMe() { console.log('Caller is', this); }
this
的值将与
调用方匹配。 如果在Node REPL内定义上述函数,则
global
对象将调用它。 如果在浏览器控制台中定义一个函数,则
window
对象将调用它。
让我们定义一个函数作为对象的属性,以使其更加清晰:
const obj = { id: '42', whoCalledMe() { console.log('Caller is', this); } };
现在,当我们在使用
obj.whoCallMe
函数时直接使用指向它的链接时,
obj
对象(由其
id
)将充当调用者:

现在的问题是:如果将
obj.whoCallMe
的链接传递给
obj.whoCallMe
,
setTimetout
?
在这种情况下,呼叫者是谁?答案将根据执行计时器功能的位置而有所不同。 在这种情况下,对呼叫者是谁的依赖是完全不能接受的。 您将失去对调用方的控制,因为这将取决于计时器的实现,在这种情况下,计时器将调用您的函数。 如果您在Node REPL中测试此代码,则
Timeout
对象将成为调用方:

请注意:仅当在常规函数中使用JavaScript
this
时,这一点才重要。 使用箭头功能时,呼叫者根本不会打扰您。
计时器问题3编写一个脚本,该脚本将以不同的延迟连续输出“ Hello World”消息。 从一秒的延迟开始,然后在每次迭代时将其增加一秒。 在第二次迭代中,延迟将为2秒。 在第三个-三个,依此类推。
在显示的消息中包括延迟。 您应该得到这样的内容:
Hello World. 1
Hello World. 2
Hello World. 3
...
限制 :只能使用const定义变量。 不使用let或var。
解决方案由于此任务中延迟的持续时间是一个变量,因此您不能在此处使用
setInterval
,但是可以在递归调用中使用
setTimeout
手动配置间隔执行。 使用
setTimeout
执行的第一个函数将创建下一个计时器,依此类推。
另外,由于您不能使用
let
/
var
,所以我们没有计数器来增加每个递归调用的延迟。 相反,您可以使用递归函数的参数在递归调用期间执行增量。
解决此问题的方法如下:
const greeting = delay => setTimeout(() => { console.log('Hello World. ' + delay); greeting(delay + 1); }, delay * 1000); greeting(1);
计时器任务4编写一个脚本,该脚本以与任务3中相同的延迟结构显示“ Hello World”消息,但是这次以5条消息为一组,并且该组将有一个主要的延迟间隔。 对于第一组5条消息,我们选择100 ms的初始延迟,接下来的-200 ms,第三次-300 ms,依此类推。
该脚本的工作方式如下:
- 在100 ms时,脚本首次显示“ Hello World”,并以100 ms为间隔增加5次。 第一条消息将在100毫秒后出现,第二条消息将在200毫秒后出现,依此类推。
- 在前5条消息之后,脚本应将主延迟增加200 ms。 因此,第6条消息将在500 ms + 200 ms(700 ms)之后显示,第7条消息-900 ms,第8条消息-在1100 ms之后显示,依此类推。
- 10条消息后,脚本应将主延迟间隔增加300 ms。 在500 ms + 1000 ms + 300 ms(18000 ms)之后应显示第11条消息。 第12条消息应在2100毫秒后显示,依此类推。
根据此原则,程序应无限期地工作。
在显示的消息中包括延迟。 您应该得到这样的内容(无评论):
Hello World. 100 // 100
Hello World. 100 // 200
Hello World. 100 // 300
Hello World. 100 // 400
Hello World. 100 // 500
Hello World. 200 // 700
Hello World. 200 // 900
Hello World. 200 // 1100
...
限制 :您只能使用对
setInterval
调用(而不能使用
setTimeout
),并且只能使用
if
。
解决方案由于我们只能使用
setInterval
调用,因此在这里我们需要使用递归,还需要增加下一个
setInterval
调用的延迟。 此外,我们需要
if
才能仅在对该递归函数进行5次调用后才能使此操作发生。
这是一个可能的解决方案:
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);
感谢所有阅读它的人。