目录
- 事件循环宏观理解
- 事件循环异步任务细分宏任务与微任务后的理解
- 结合流程图与代码理解览器事件循环
首先来介绍一些谷歌浏览器的进程与线程。
谷歌浏览器包含五个进程:浏览器主进程、渲染进程、GPU进程、网络进程、插件进程。
其中最重要的渲染进程包含五个线程:渲染线程、js引擎线程、事件触发回调线程、定时器触发线程、http请求线程。
由于js是单线程,所以在js引擎线程外才会分出事件回调、定时器、http请求三个线程用于执行对应的异步任务,由js引擎线程来执行同步任务以及异步结束后的任务。
事件循环宏观理解
js执行同步任务和异步任务的流程:首先js引擎线程(主线程)在执行js任务时会有一个执行栈,js代码从上到下依次执行时遇到的同步任务会推入执行栈中执行,遇到异步任务会先在主线程执行一点,然后会推入对应的线程执行异步代码,在主线程之外存在一个任务队列,所有异步任务在对应线程执行完后会进入任务队列处于等待状态,在主线程的执行栈执行完后会读取任务队列,然后取出任务队列的第一个事件执行,这个事件对应的异步任务结束等待状态,进入执行栈,开始执行。然后主线程会一直重复上面的操作(执行执行栈中所有同步任务,然后再执行任务队列的第一个事件,一直重复)
事件循环异步任务细分宏任务与微任务后的理解
异步任务又分为宏任务和微任务(也就是任务队列分为宏任务队列和微任务队列):
- 宏任务:整体代码(全局)、setTimeout 和 setInterval、setImmediate(Node.js 中)、I/O 操作(鼠标事件、键盘事件、网络事件等)、UI 渲染
- 微任务:Promise.then(Promise 的回调)、process.nextTick(Node.js中)、MutationObserver(DOM变动观察)
宏任务与微任务的区别在于任务队列中事件的执行优先级,宏任务的优先级要高于微任务。
在一个事件循环中,异步事件返回结果后会被放到任务队列中。然而,根据这个异步事件的类型,这个事件实际上会放到对应的宏任务队列或者微任务队列中去。进入整体代码(宏任务)后,开始首次事件循环,当执行栈清空后(也就是同步任务执行完,开始进入任务队列执行异步任务事件时),事件循环机制会优先检测微任务队列中的事件并推至主线程执行,当微任务队列清空后,才会去检测宏任务队列中的事件,再将宏任务队列的第一个事件推至主线程中执行,然后开始第二次事件循环,执行栈清空所有同步任务,事件循环机制又会检测微任务队列,再去宏任务队列,如此反复循环。
我们只需记住当当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
结合流程图与代码理解览器事件循环
- 第一个
<script>
标签的代码是第一个宏任务(这里可以把同步任务理解为第一个宏任务) process.nextTick
优先级高于Promise.then
图解:
事件循环完整流程:执行栈执行所有同步任务(也可以看作是一次宏任务)->进入微任务队列执行所有微任务事件->进入宏任务队列执行排在第一位的事件;然后再以次重复下去形成浏览器的事件循环
结合代码理解:
// 同步任务1
console.log(1);
setTimeout(() => {
// 宏任务
console.log(2);
}, 0);
let promise = new Promise((res) => {
// 同步任务2
console.log(3);
resolve();
})
.then((res) => {
// 微任务1
console.log(4);
})
.then((res) => {
// 微任务2
console.log(5);
});
// 同步任务3
console.log(6);
// 1 3 6 4 5 2
// 执行栈先执行所有同步任务,再进入微任务队列执行所有微任务,最后再执行宏任务队列的第一个