我认为我们不能脱离堆栈来讨论事件循环,所以:
JS 有三个“栈”:
- 所有同步调用的标准堆栈(一个函数调用另一个函数等)
- 用于所有具有更高优先级的异步操作的微任务队列(或作业队列或微任务堆栈)(process.nextTick、Promises、Object.observe、MutationObserver)
- 宏任务队列(或事件队列、任务队列、宏任务队列)用于所有具有较低优先级的异步操作(setTimeout、setInterval、setImmediate、requestAnimationFrame、I/O、UI 渲染)
|=======|
| macro |
| [...] |
| |
|=======|
| micro |
| [...] |
| |
|=======|
| stack |
| [...] |
| |
|=======|
事件循环是这样工作的:
- 从堆栈中从下到上执行所有内容,并且仅当堆栈为空时,检查上面队列中发生的事情
- 检查微堆栈并在堆栈的帮助下执行那里的所有内容(如果需要),一个接一个微任务,直到微任务队列为空或不需要任何执行,然后才检查宏堆栈
- 检查宏堆栈并在堆栈的帮助下执行那里的所有内容(如果需要)
如果堆栈不为空,则不会触及微堆栈。如果微堆栈不为空或不需要任何执行,则不会触及宏堆栈。
总结一下:微任务队列与宏任务队列几乎相同,但那些任务(process.nextTick、Promises、Object.observe、MutationObserver)比宏任务具有更高的优先级。
微观就像宏观,但具有更高的优先级。
在这里,您拥有理解一切的“终极”代码。
console.log('stack [1]');
setTimeout(() => console.log("macro [2]"), 0);
setTimeout(() => console.log("macro [3]"), 1);
const p = Promise.resolve();
for(let i = 0; i < 3; i++) p.then(() => {
setTimeout(() => {
console.log('stack [4]')
setTimeout(() => console.log("macro [5]"), 0);
p.then(() => console.log('micro [6]'));
}, 0);
console.log("stack [7]");
});
console.log("macro [8]");
JavaScript是高级单线程语言,解释型语言。这意味着它需要一个将 JS 代码转换为机器代码的解释器。解释器的意思是引擎。用于 chrome 的 V8 引擎和用于 safari 的 webkit。每个引擎都包含内存、调用堆栈、事件循环、计时器、Web API、事件等。
事件循环:微任务和宏任务
事件循环的概念非常简单。有一个无限循环,JavaScript 引擎等待任务,执行它们然后休眠,等待更多任务
任务被设置 - 引擎处理它们 - 然后等待更多任务(同时休眠并消耗接近零的 CPU)。可能会发生在引擎繁忙时任务到来,然后将其排队的情况。任务形成一个队列,即所谓的“宏任务队列”
微任务完全来自我们的代码。它们通常由Promise创建: .then/catch/finally 处理程序的执行成为一个微任务。微任务也在 await 的“掩护下”使用,因为它是Promise处理的另一种形式。在每个宏任务之后,引擎会立即执行微任务队列中的所有任务,然后再运行任何其他宏任务或渲染或其他任何东西