JavaScript是一门单线程的语言,因此,JavaScript在同一个时间只能做一件事,单线程意味着,如果在同个时间有多个任务的话,这些任务就需要进行排队,前一个任务执行完,才会执行下一个任务。
在JS引擎线程中, “多线程”和异步操作是通过事件循环(Event Loop)来实现的。
- 事件循环由三部分组成:
- 调用栈:存放要执行的函数
- 消息队列:存放宏任务的函数
- 微任务队列:存放微任务的函数
事件循环刚开始时(其实,就是当前宏任务中所有的同步任务,因为script整段代码就是宏任务),会从全局栈代码开始一行一行执行,遇到函数调用就把函数压入调用栈中,被压入的函数叫做帧(Frame),函数执行完毕后弹出调用栈。
JavaScript中的异步操作:
宏任务:AJAX、事件回调、setTimeout中的回调函数、setInterval中的回调函数,会入队到消息队列中,消息会在调用栈清空后,被压入到调用栈中执行。
微任务:Promise.then的回调函数、async await等,入队到微任务队列中,在调用栈被清空后立即执行(在宏任务前执行),且执行过程中产生的新的微任务会在此次循环中一同执行。
关于一轮事件循环始末
一次事件循环:本次宏任务、本次宏任务产生的微任务、以及本次微任务产生的微任务全部执行结束,本次事件循环结束。本次事件循环产生的宏任务将在下一次事件循环中执行。
例如:
<script>
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
</script>
// 结果:
// one
// two
// three
一轮事件循环:
- 宏任务为本段script代码
- 遇到setTimeout,为宏任务,将setTimeout的回调函数放入消息队列,将在下一轮事件循环中执行
- 遇到Promise.then,为微任务,将then的回调函数放入微任务队列
- 遇到console.log(‘one’),压入调用栈,执行,输出one,弹出调用栈
- 此时调用栈为空,立即将微任务队列中的then回调函数压入调用栈,执行,输出two,弹出调用栈
- 调用栈为空
--------------------------------------本轮事件循环结束----------------------------------------------- - 新一次事件循环开始,将消息队列中setTimeout的回调函数压入调用栈中,执行,输出three,弹出调用栈
- 调用栈为空
--------------------------------------本轮事件循环结束-----------------------------------------------