在浏览器和 Node 中 Event Loop 其实是不相同的。
浏览器中的 Event Loop
参考(二)js引擎执行过程的理解-执行阶段!!
事件处理机制 - 浏览器的事件循环(轮询)模型
![](https://i-blog.csdnimg.cn/blog_migrate/d474ea12adeed14ad6efb01d3a4bb5ce.png)
相关重要概念:
- 执行栈execution stack:所有的代码都是在此空间中执行的
- 浏览器内核:browser core,js引擎模块(在主线程处理),其它模块(在主/分线程处理)
3.任务队列,消息队列,事件队列:都是同一个 callback queue - 事件轮询event loop:从任务队列中循环取出回调函数放入执行栈中处理(一个接一个)
- 事件驱动模型event-driven interaction model
- 代码分类
初始化执行代码: 包含绑定dom事件监听, 设置定时器(定时器里的回调函数是属于另一种), 发送ajax请求的代码
回调执行代码: 处理回调逻辑 - js引擎执行代码的基本流程:
初始化代码===>回调代码 - 模型的2个重要组成部分:
事件管理模块
回调队列 - 模型的运转流程
执行初始化代码, 将事件回调函数交给对应模块管理
当事件发生时, 管理模块会将回调函数及其数据添加到回调列队中
只有当初始化代码执行完后(可能要一定时间), 才会遍历读取回调队列中的回调函数执行
- Micro-Task 与 Macro-Task
浏览器端事件循环中的异步队列有两种:macro(宏任务)队列和 micro(微任务)队列。
宏任务队列可以有多个,微任务队列只有一个
当前执行栈执行完毕,会立即先处理所有微任务队列中的事件,再去宏任务队列中取出一个事件
宏任务 -> 微任务 -> 宏任务
在一次事件循环中,微任务永远在宏任务之前执行
- 常见的 macro-task :setTimeout、setInterval、script(整体代码)、 I/O 操作、UI 渲染, postMessage, MessageChanel, setImmediate(nodejs环境)。
- 常见的 micro-task : new Promise().then(回调)、MutationObserver(html5新特性) , process.nextTick(nodejs环境)。【单独记这个】
- Event Loop 过程解析
一个完整的 Event Loop 过程,可以概括为以下阶段:
![](https://i-blog.csdnimg.cn/blog_migrate/3076ca48ee8469bd9c160f6f99255d90.png)
-
一开始执行栈空,我们可以把执行栈认为是一个存储函数调用的栈结构,遵循先进后出的原则。micro 队列空,macro 队列里有且只有一个 script 脚本(整体代码)。
-
全局上下文(script 标签)被推入执行栈,同步代码执行。在执行的过程中,会判断是同步任务还是异步任务,通过对一些接口的调用,可以产生新的 macro-task 与 micro-task,它们会分别被推入各自的任务队列里。同步代码执行完了,script 脚本会被移出 macro 队列,这个过程本质上是队列的 macro-task 的执行和出队的过程。
-
上一步我们出队的是一个 macro-task,这一步我们处理的是 micro-task。但需要注意的是:当 macro-task 出队时,任务是一个一个执行的;而 micro-task 出队时,任务是一队一队执行的。因此,我们处理 micro 队列这一步,会逐个执行队列中的任务并把它出队,直到队列被清空。
-
执行渲染操作,更新界面
-
检查是否存在 Web worker 任务,如果有,则对其进行处理
-
上述过程循环往复,直到两个队列都清空
我们总结一下,每一次循环都是一个这样的过程:
当某个宏任务执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务,依次类推。
接下来我们看道例子来介绍上面流程:
Promise.resolve().then(()=>{
console.log('Promise1')
setTimeout(()=>{
console.log('setTimeout2')
},0)
})
setTimeout(()=>{
console.log('setTimeout1')
Promise.resolve().then(()=>{
console.log('Promise2')
})
}