事件循环是浏览器(或其他 JavaScript 主机)运行的循环,从 事件队列(更一般地说是一个 job 或 task 队列,该队列列出等待处理的事件/作业/任务)并处理它,然后返回下一个并处理它,等等。 p>
用伪代码术语(省略很多细节):
// Event/job/task loop:
while (running) {
const task = taskQueue.pop();
if (task) {
task();
}
}
JavaScript 代码或主机环境事件可能会将作业/任务添加到队列中,以便在循环到达它们时将其拾取。
HTML 规范详细介绍了进程here。乍一看,在 HTML 规范中包含这些信息似乎很奇怪,但该规范不仅描述了 HTML(标记语言),还描述了用户代理(包括浏览器)应如何处理/呈现网页。
JavaScript 规范讨论了这个here,尽管它没有详细说明主机应该执行什么样的循环,因为这是特定于主机的。
作为非浏览器 JavaScript 主机的示例,Node.js 文档描述了它的循环 here。
上面的伪代码中遗漏的一个更重要的细节是,几乎所有 JavaScript 主机中都至少有 两个 队列:主要的一个(被 HTML 规范称为“任务队列”) ) 和一个用于承诺反应和可选的其他事情(被 HTML 规范称为“微任务队列”)。所以这个工作循环看起来更像:
// Event/job/task loop:
while (running) {
const task = taskQueue.pop();
if (task) {
task();
// Microtask loop:
while (!microtaskQueue.isEmpty()) {
const microtask = microtaskQueue.pop();
microtask();
}
}
}
注意微任务队列是如何在每个任务之后清空的,这意味着如果任务队列中有任务 1、任务 2 和任务 3,并且在任务 1 的处理过程中,微任务队列中会添加两个微任务,这些微任务在任务 1 结束时运行,在任务 2 之前。这是一个示例:setTimeout 将任务排队,但承诺反应(调用履行或拒绝处理程序)是微任务:
// Task 1
console.log("Queuing Task 1");
setTimeout(() => {
console.log("Task 1 - start");
// Note: The promise handling below is atypical, tailored specifically to
// this example. Normally you chain promises, and you handle rejections.
// Note that the below says we're "queuing Microtask X" because we know
// that the promise is already fulfilled. Normally, when you call `then`,
// you *don't* know whether the promise is fulfilled yet, so the microtask
// may be added when you call `then`, or may be added later when the
// promise is fulfilled
const p = Promise.resolve();
console.log("Task 1 queuing Microtask A");
p.then(() => {
console.log("Microtask A");
});
console.log("Task 1 queuing Microtask B");
p.then(() => {
console.log("Microtask B");
});
console.log("Task 1 - end");
}, 0);
// Task 2
console.log("Queuing Task 2");
setTimeout(() => {
console.log("Task 2");
}, 0);
// Task 3
console.log("Queuing Task 3");
setTimeout(() => {
console.log("Task 3");
}, 0);
.as-console-wrapper {
max-height: 100% !important;
}