事件循环的概念是通用的还是特定于语言的?我正在寻找带有示例的详细说明,以清楚地理解以下内容:
1.如何运作?
如何/何时/可以/如何利用JavaScript中的事件循环?
ECMAScript-6 / 2015中有关事件循环的任何更改?
更新:
对此并没有答案,但是我正在寻找一个带有示例的易于理解的定义。
附录:
考虑以下代码:
var list = readHugeList();
var nextListItem = function() {
var item = list.pop();
if (item) {
// process the list item...
setTimeout( nextListItem, 0);
}
};
假设setTimeout()在这里使用事件循环来防止stackoverflow是否正确?我阅读了以下代码的解释:
由于事件循环处理递归而不是调用堆栈,因此消除了堆栈溢出。
更新2
上面的代码中对setTimeout()的完整解释如下:
由于事件循环处理递归而不是调用堆栈,因此消除了堆栈溢出。当nextListItem运行时,如果item不为null,则将超时函数(nextListItem)推送到事件队列中,并且该函数退出,从而使调用堆栈保持清晰。当事件队列运行其超时事件时,将处理下一个项目,并将计时器设置为再次调用nextListItem。因此,该方法从头到尾都进行了处理,而没有直接的递归调用,因此无论迭代次数如何,调用堆栈都保持清晰。
前面提到的解释和这里的答案中的矛盾,使得它变得更加难以理解。
最佳答案
事件循环的概念是通用的还是特定于语言的?
在最高级别,一般。但是不同的环境将以不同的方式实现细节,因此实际上您需要特定于环境的信息来了解正在发生的事情。
怎么运行的?
所有这些细节都在规范中,主要是在Jobs and Job Queues部分中。
有几个关键方面:
有一系列作业(JavaScript规范的术语)或“任务”(如HTML规范所称)正在等待JavaScript引擎运行。引擎完成一项工作后,它将在队列中的下一个工作/任务(如果有)上工作。
作业一旦开始,它将一直运行直到完成;没有其他作业可以中断它。这称为“运行完成”,对于定义JavaScript的工作方式非常重要。
作业队列按顺序处理。
因此,请考虑以下代码:
console.log("one");
setTimeout(function() {
console.log("two");
}, 1000);
如果您运行该代码(通过NodeJS或浏览器),则会发生以下情况(忽略一些不相关的细节):
一开始,队列为空,JavaScript引擎为空闲
环境(NodeJS,浏览器)将作业排队以运行脚本
JavaScript引擎开始工作并运行脚本:
输出“一个”
它为我们赋予
setTimeout
的匿名函数设置了一个计时器工作结束
在某个时候,环境中的计时器机制确定是时候调用回调了,因此它将作业排队来调用它
JavaScript引擎从队列中提取该作业并运行该功能
输出“两个”
工作结束
现在考虑以下代码:
console.log(Date.now(), "one");
setTimeout(function first() {
console.log(Date.now(), "two");
}, 500);
setTimeout(function second() {
var end = Date.now() + 1000;
while (end > Date.now()) {
// Busy-wait (normally this is a Bad Thing™, I'm using it here
// to simulate actual work that takes significant time
}
}, 100);
如您所见,它在500ms安排了一个定时回调,然后在100ms安排了另一个。但是回调中100ms的代码至少需要1000ms才能运行。怎么了?
环境将作业排队以运行脚本
JS引擎负责这项工作
输出时间值和“ 1”,例如
1470727293584 one
设置对函数
first
的定时回调,持续500ms设置对函数
second
的定时回调,持续100ms工作结束
大约100毫秒后,环境将作业排队以运行
second
JavaScript引擎开始工作并运行功能
它开始工作(好吧,所以实际上只是忙于等待)
大约400毫秒(从设置计时器的时间起500毫秒)之后,环境将作业排队以调用
first
;由于JavaScript引擎正忙于上一个作业,因此该作业位于队列中同时,JavaScript引擎仍在执行
first
的工作:最终,由#3的JavaScript引擎完成的工作已经完成
工作完成
JavaScript引擎从队列中提取下一个作业,并运行对
second
的调用它输出时间值和“两个”,例如
1470727294687 two
工作结束
注意,环境在JavaScript忙时做了一些事情。它排队要完成的工作。
在JavaScript引擎繁忙时环境可以执行操作的事实非常重要。
(值得注意的是,在排队作业时,某些环境可能不一定将作业添加到队列的末尾;例如,在某些浏览器中,“队列”实际上是多个优先级略有不同的队列...)
我如何/何时/可以利用它?
它并没有充分利用它,而是知道它在那里。同样重要的是要注意,尽管JavaScript具有“从运行到完成”的语义,但是即使JavaScript代码正在运行,运行JavaScript的环境也可能同时在做其他事情。
ECMAScript-6 / 2015中有关事件循环的任何更改?
虽然在规范中比以前更完整地定义了它,但实际上不是。可能与更改最接近的是与承诺的关系:使用
then
或catch
计划的回调将始终作为作业排队,而永远不会同步运行。这是JavaScript规范第一次定义异步发生的事情(ajax和计时器不是JavaScript规范的一部分)。在下面,您已经问过:
考虑以下代码:
var list = readHugeList();
var nextListItem = function() {
var item = list.pop();
if (item) {
// process the list item...
setTimeout(nextListItem, 0);
}
};
假设setTimeout()在这里使用事件循环来防止stackoverflow是否正确?
(我假设在那之后立即有一个
nextListItem();
调用。)不,但是它在做其他重要的事情。该版本的非
setTimeout
版本如下所示:1var list = readHugeList();
while (list.length) {
var item = list.pop();
// process the list item...
}
这是一个简单的循环,没有堆栈溢出的可能。
它正在做的事情是与事件循环协同工作,避免了长时间运行的工作会阻塞JavaScript引擎,从而阻止它处理任何其他工作(例如I / O完成,单击或其他事件)。因此,它通过一次处理一个项目将工作分解成一些小任务,有助于确保整个工作队列都得到继续处理。这意味着处理列表要花费更长的时间,但同时不会阻塞其他内容。
1确实,该代码的非
setTimeout
版本看起来像这样:var list = readHugeList();
var nextListItem = function() {
var item = list.pop();
if (item) {
// process the list item...
// then recurse
nextListItem();
}
};
nextListItem();
...在那种情况下,可能会产生堆栈溢出,但这是编写该代码的非常奇怪的方式。
关于javascript - 如何利用JavaScript和Node.js中的事件循环?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/38844135/