事件循环机制的作用
事件循环机制是 JS 的一种执行机制,一种可以实现异步编程的机制。
因为 JS 是单线程的,单线程意味着所有任务需要排队执行。但是有一些 API(比如:定时器和 Ajax 等)是需要等待一定的时间才能得到结果的,如果它们按顺序执行,会造成很大的效率问题。因此,JS 采取了异步编程,凡是需要等待的操作,都会交给浏览器或 NodeJS 进行挂起,等同步代码执行完毕后再执行异步操作。这个过程就需要用到事件循环机制来进行操作的调度。
事件循环机制的原理
事件循环机制将所有代码分为同步任务和异步任务,异步任务又分为宏任务和微异步。事件循环机制就是围绕同步任务、宏任务、微任务的执行时机展开的。
同步任务在调用栈中执行,异步任务在任务队列中执行。
常见的宏任务
- 整体代码(<script>) :整个脚本代码块的执行可以视为一个宏任务
- setTimeout / setlnterval:定时器回调函数属于宏任务
- setlmmediate (Node.js环境): Node.js 独有的宏任务接口,表示下一次事件循环迭代开始时执行的任务
- I/O 回调:如网络请求、文件读写等异步 I/O 操作完成后的回调函数
- UI 渲染:浏览器环境中的渲染操作,如重绘(repaint)和回流(reflow)
- MessageChannel:使用 MessageChannel 进行跨文档消息传递时的回调
- MutationObserver:虽然 MutationObserver 的行为更像是微任务,但它实际上是在 UI 渲染之后执行的,可以看作是宏任务的一部分(因为它依赖于 UI 更新)
常见的微任务
- Promise.then/catch/finally:Promise对象的回调函数。(Promise构造函数的参数中的代码是同步的,.then 回调函数的内容是异步的)
- MutationObserver 回调:虽然它在宏任务之后执行,但其回调属于微任务
- Process.nextTick (Node.js环境):Node.js 环境提供的微任务接口,确保回调函数在当前宏任务结束前执行
- async/await:async function 内部的 await 表达式之后的代码块会在下一个微任务中执行
事件循环机制的执行流程
- 初始化:当 JS 引擎启动时,首先会创建全局执行上下文并压入调用栈。初始化任务队列,包括微任务队列和宏任务队列
- 执行同步代码:使用调用栈执行同步代码,遇到宏任务放入宏任务队列,遇到微任务放入微任务队列。
- 执行微任务:当全局上下文中的所有同步代码执行完毕后,会首先处理所有待执行的微任务
- 渲染(浏览器环境):清空微任务队列后,浏览器会尝试进行渲染操作,更新页面视图。
- 执行宏任务:渲染之后,事件循环会从宏任务队列中取出一个宏任务来执行。 每个宏任务内部也会先执行其同步代码,同样遇到宏任务放入宏任务队列,遇到微任务放入微任务队列。
- 执行微任务:若上一步宏任务中产生微任务,微任务队列不为空,需处理所有待执行的微任务
- 渲染
- 执行宏任务
- 循环迭代:事件循环不断在宏任务队列和微任务队列间切换,执行任务,直至两个队列都为空
浏览器执行的时候,所有的js代码都会被解析到<script>标签中,而<script>标签又是一个宏任务,所以整个事件循环机制可以看作是在解析一个全局的宏任务。