事件循环的概念是通用的还是特定于语言的?
在最高级别,将军。但是不同的环境会以不同的方式实现细节,以至于您确实需要特定于环境的信息才能知道发生了什么。
它是如何工作的?
所有血淋淋的细节都在规范中,主要在名为Jobs and Job Queues的部分。
有几个关键方面:
有一个 队列 等待由JavaScript 引擎。当引擎完成一项作业时,它会开始处理队列中的下一个作业/任务(如果有)。
一旦作业开始,它会一直运行直到完成;没有其他工作可以打断它。这称为 run-to-completion,对于定义 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);
如您所见,它在 500 毫秒安排一个定时回调,然后在 100 毫秒安排另一个。但是 100 毫秒回调中的代码至少需要 1000 毫秒才能运行。会发生什么?
- 环境将作业排队以运行脚本
- JS 引擎接手这项工作
- 输出时间值和“一”,比如
1470727293584 one
- 为函数
first 设置一个定时回调,在未来 500 毫秒内
- 为函数
second 设置一个定时回调,在未来 100 毫秒内
- 工作结束
- 大约 100 毫秒后,环境将作业排队运行
second
- JavaScript 引擎获取作业并运行函数
- 大约 400 毫秒后(从设置定时器开始的 500 毫秒),环境排队一个作业来调用
first;由于 JavaScript 引擎正忙于上一个作业,因此该作业位于队列中
- 与此同时,JavaScript 引擎仍在处理调用
first 的工作:
- JavaScript 引擎从 #3 开始所做的工作终于完成了
- 工作完成
- JavaScript 引擎从队列中选择下一个作业并运行对
second 的调用
- 它输出时间值和“二”,比如
1470727294687 two
- 工作结束
请注意,环境在 JavaScript 忙碌时做了一些事情;它排队等待完成的工作。
环境可以在 JavaScript 引擎忙碌时做事这一事实非常重要。
(值得注意的是,在排队作业时,某些环境可能不一定将作业添加到队列的end;例如在某些浏览器中,“队列”实际上不止一个优先级略有不同的队列...)
如何/何时应该/我可以利用它?
与其说是利用,不如说是知道它的存在。还需要注意的是,虽然 JavaScript 具有 run-to-completion 语义,但它运行的环境可能同时在做其他事情,即使在 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 版本看起来像这样:1
var 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();
...在这种情况下,可能会发生堆栈溢出,但编写该代码的方式会非常奇怪。