
简介: JavaScript计时器是窗口的对象接口,而不是JavaScript的一部分. 它的功能是由浏览器实现的,并且在不同的浏览器中会有所不同. 计时器也可以由node.js运行时本身实现.
几周前我在Twitter上发布了这样的采访问题:
JavaScript面试问题:
在哪里可以找到setTimeout和setInterval的源代码? (他们在哪里实现的?)
您在面试中如何回答? (您无法搜索)
函数setTimeOut(回调
Twitter的答案的前半部分是错误的. 答案不是V8(或其他虚拟机!)android 全局定时器,尽管著名的“ JavaScript计时器”功能(如setTimeout和setInterval)不是ECMAScript规范或任何JavaScript实现的一部分. 计时器功能由浏览器实现,不同浏览器的实现方式会有所不同. 计时器也可以由Node.js运行时本身实现.
浏览器中的主要计时器功能是Window对象的接口. Window对象还具有许多其他方法和对象. 此接口使所有元素在JavaScript的全局范围内可用. 这就是为什么您可以直接在浏览器控制台中执行setTimeout的原因.
在节点中,计时器是全局对象的一部分,这与浏览器中的Window非常相似. 您可以在Node中查看计时器的源代码.
有些人可能认为这是一个不好的面试问题-为什么您必须知道这个问题? !作为JavaScript开发人员,我认为您应该知道这一点,因为如果您不这样做,则可能表示您不完全了解V8(和其他虚拟机)如何与浏览器和Node交互.
让我们开始做一些有关计时器功能的例子和挑战吗?
更新: 本文现在是“ Node.js的完整介绍”的一部分. 您可以在此处阅读最新版本.
计时器功能是高阶功能,可用于延迟或重复执行其他功能(它们作为第一个参数接收).
这是延迟的示例:
// example1.js
setTimeout(
() => {
console.log('Hello after 4 seconds');
},
4 * 1000
);
此示例使用setTimeout将问候语延迟4秒. setTimeout的第二个参数是延迟(多少毫秒). 这就是为什么我用4 * 1000代表4秒
setTimeout的第一个参数是一个将延迟执行的函数

如果在节点环境中执行example1.js. 节点将暂停4秒钟,然后打印问候语(然后退出).
请注意,setTimeout的第一个参数只是一个函数引用. 它不必像example1.js这样的内联函数. 这是不使用内联函数的相同示例:
const func = () => {
console.log('Hello after 4 seconds');
};
setTimeout(func, 4 * 1000);
如果使用setTimeout延迟的函数需要携带参数,我们可以将参数放入setTimeout(在两个已知参数之后),以将参数传递给需要延迟的函数.
// For: func(arg1, arg2, arg3, ...) // We can use: setTimeout(func, delay, arg1, arg2, arg3, ...)
例如:
// example2.js
const rocks = who => {
console.log(who + ' rocks');
};
setTimeout(rocks, 2 * 1000, 'Node.js');
上面的代码以2秒的延迟执行,接收who参数并将字符串“ Node.js”通过setTimeout传递给函数的who参数.
在节点环境中执行example2.js控制台将在2秒后显示“ Node.js岩石”
使用到目前为止所学的关于setTimeout的知识,并在相应的延迟后打印以下2条消息.
约束: 您只能在解决方案中定义一个函数,包括内联函数. 这意味着许多setTimeout调用必须使用完全相同的函数.
这是我解决此挑战的方法:
// solution1.js
const theOneFunc = delay => {
console.log('Hello after ' + delay + ' seconds');
};
setTimeout(theOneFunc, 4 * 1000, 4);
setTimeout(theOneFunc, 8 * 1000, 8);
我要求OneFunc接收延迟参数,并在打印的消息中使用延迟参数的值. 这样,该函数可以根据传递给它的任何延迟值打印不同的消息.
然后,我在两次调用setTimeout中使用了oneFunc,一个在4秒后触发,另一个在8秒后触发. 这两个setTimeout调用还获得了第三个参数,以表示OneFunc的delay参数.
使用node命令执行solution1.js文件将打印出质询所需的内容,第一条消息在4秒钟后,第二条消息在8秒钟后.
如果我要求您每4秒打印一条消息怎么办?

尽管您可以将setTimeout放入循环中,但定时器API还提供了setInterval函数,该函数可以满足始终执行某项操作的要求.
这是setInterval的示例:
// example3.js
setInterval(
() => console.log('Hello every 3 seconds'),
3000
);
此示例将每3秒打印一条消息. 使用node命令执行example3.js将使Node永远打印此消息,直到您终止该过程(使用CTRL + C).
由于调用计时器函数可以调度操作,因此您可以在执行操作之前取消该操作.
对setTimeout的调用返回一个计时器“ ID”,可以将计时器ID与clearTimeout调用一起使用来取消计时器. 这是这个例子:
// example4.js
const timerId = setTimeout(
() => console.log('You will not see this one!'),
0
);
clearTimeout(timerId);
这个简单的计时器应在“ 0”毫秒后触发(使其立即生效),但不会,因为我们正在捕获timerId值并在使用clearTimeout调用后立即将其取消.
当我们使用node命令执行example4.js时,Node将不打印任何内容,并且进程将退出.
在Node.js中,还有另一种使用0ms来执行setTimeout的方法. Node.js计时器API具有另一个名为setImmediate的函数,该函数与setTimeout基本上相同,但为0ms,但我们不必在那里指定延迟:
setImmediate(
() => console.log('I am equivalent to setTimeout with 0 ms'),
);
并非所有浏览器都支持setImmediate方法. 不要在前端代码中使用它.
像clearTimeout一样,还有一个clearInterval函数android 全局定时器,它对setInerval调用和clearImmediate调用执行相同的操作.
在前面的示例中,您是否注意到用setTimeout在“ 0”毫秒后执行内容并不意味着立即(在setTimeout行之后)执行内容,而是在脚本中的所有其他内容立即执行后(包括clearTimeout调用)立即执行?
让我用一个例子清楚地说明这一点. 这是一个简单的setTimeout调用,应在半秒钟后触发,但不会:
// example5.js
setTimeout(
() => console.log('Hello after 0.5 seconds. MAYBE!'),
500,
);
for (let i = 0; i < 1e10; i++) {
// Block Things Synchronously
}
在此示例中定义了计时器之后,我们使用了一个大的for循环来同步运行时. 1e10是1之后的10个零,因此循环是一个100亿的滴答周期(基本上是模拟一个繁忙的CPU). 在此循环中,节点无法执行任何操作.

这在实践中当然是一件很糟糕的事情,但是它将帮助您理解setTimeout延迟不是保证的事情,而是最小的事情. 500ms表示最小延迟为500ms. 实际上,该脚本将花费更长的时间来打印其问候语. 它必须等待阻塞循环完成.
编写脚本以每秒打印一次消息“ Hello World”,但仅打印5次. 5次后,脚本应打印消息“完成”,并让节点进程退出.
限制: 您不能使用setTimeout调用来完成此挑战. 提示: 您需要一个柜台.
这是我解决此问题的方法:
let counter = 0;
const intervalId = setInterval(() => {
console.log('Hello World');
counter += 1;
if (counter === 5) {
console.log('Done');
clearInterval(intervalId);
}
}, 1000);
我将计数器值设为0,然后在捕获其ID的同时启动setInterval调用.
延迟功能将打印消息并每次增加计数器. 在delay函数中,if语句将检查我们是否在5次内. 如果是这样,它将打印“完成”并使用捕获的intervalId常数清除间隔. 间隔延迟为“ 1000”毫秒.
在常规函数中使用JavaScript的this关键字时,它看起来像这样:
function whoCalledMe() {
console.log('Caller is', this);
}
此关键字中的值将表示函数的调用方. 如果以上功能在Node REPL中定义,则调用方将是全局对象. 如果该功能是在浏览器的控制台中定义的,则调用者将是窗口对象.
让我们将函数定义为对象的属性以使其更清晰:
const obj = {
id: '42',
whoCalledMe() {
console.log('Caller is', this);
}
};
// The function reference is now: obj.whoCallMe
现在,当您使用其引用直接调用obj.whoCallMe函数时,调用者将是obj对象(由其ID标识):

现在,问题是,如果我们将obj.whoCallMe的引用传递给setTimetout调用,调用者将是什么?
// What will this print?? setTimeout(obj.whoCalledMe, 0);
在这种情况下谁是呼叫者?

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

请注意,只有在常规函数中使用JavaScript的this关键字时,这才有意义. 如果使用箭头功能,则完全不必担心调用方.
编写脚本,以不同的延迟连续打印消息“ Hello World”. 从1秒的延迟开始,然后每次增加1秒的延迟. 第二次时间将延迟2秒. 第三次将延迟3秒,依此类推.
延迟时间包含在打印的消息中. 预期的输出如下:
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);
编写脚本以连续打印消息“ Hello World”,该消息具有与Challenge#3相同的可变延迟概念,但是这次是每个主要延迟间隔5个消息组. 从前5条消息的延迟时间为100毫秒开始,接下来的5条消息的延迟时间为200毫秒,然后是300毫秒,依此类推.
以下是代码要求:
在打印的消息中包含延迟. 预期的输出如下所示(不带注释):
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...
约束: 您只能使用setInterval调用(不能使用setTimeout),并且只能使用一个if语句.
因为我们只能使用setInterval调用,所以我们还需要递归以增加下一个setInterval调用的延迟. 此外,仅在调用递归函数5次之后,才需要一条if语句来控制此操作.
以下是解决方案之一:
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);
感谢您阅读.
本文是从博客园转移过来的,原始地址为:
本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/jisuanjixue/article-229764-1.html
我也在不断的去追求
送到家的恶狗不打