我认为我们无法将事件循环讨论与堆栈分开,所以:
JS有三个“堆栈”:
- 标准堆栈用于所有同步调用(一个函数调用另一个函数等)
- 微任务队列(或作业队列或微任务堆栈)用于所有具有更高优先级的异步操作(process.nextTick,Promises,Object.observe,MutationObserver)
- 宏任务队列(或事件队列、任务队列、宏任务队列)用于所有具有较低优先级的异步操作(setTimeout,setInterval,setImmediate,requestAnimationFrame,I/O,UI渲染)
|=======|
| macro |
| [...] |
| |
|=======|
| micro |
| [...] |
| |
|=======|
| stack |
| [...] |
| |
|=======|
事件循环的工作方式如下:
- 从栈底到栈顶执行所有任务,仅当栈为空时,才检查上面的队列中有什么需要执行
- 检查微任务队列并使用栈帮助执行其中的所有任务(如果需要),一个接一个地执行直到微任务队列为空或不需要执行,然后再检查宏任务队列
- 检查宏任务队列并使用栈帮助执行其中的所有任务(如果需要)
只有当栈为空时,才会处理微任务队列。只有当微任务队列为空或不需要执行时,才会处理宏任务队列。
总结一下:微任务队列与宏任务队列几乎相同,但是那些任务(process.nextTick, Promises, Object.observe, MutationObserver)比宏任务具有更高的优先级。
微任务与宏任务类似,但优先级更高。
这里有一段“终极”代码可帮助您理解所有内容。
<div data-lang="js" data-hide="false" data-console="true" data-babel="false" class="snippet">
<div class="snippet-code">
<pre class="snippet-code-js lang-js prettyprint-override"><code>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]");
/* Result:
stack [1]
macro [8]
stack [7], stack [7], stack [7]
macro [2]
macro [3]
stack [4]
micro [6]
stack [4]
micro [6]
stack [4]
micro [6]
macro [5], macro [5], macro [5]
--------------------
but in node in versions < 11 (older versions) you will get something different
stack [1]
macro [8]
stack [7], stack [7], stack [7]
macro [2]
macro [3]
stack [4], stack [4], stack [4]
micro [6], micro [6], micro [6]
macro [5], macro [5], macro [5]
more info: https://blog.insiderattack.net/new-changes-to-timers-and-microtasks-from-node-v11-0-0-and-above-68d112743eb3
*/
while (task = todo.shift()) task();
- Bergi