对于我们前端工程师来说,事件循环 这个词应该是耳熟能详了,由于它会直接影响我们平时的开发,因此距离我们很近,同时它也是面试高频问题,所以一定要掌握它
事件循环在 浏览器环境 和 node环境 下有不同的实现,本篇文章也会分别做介绍,废话不多,开搞!
什么是事件循环
众所周知,js引擎是 单线程,主线程上的任务都是放入 调用栈 中依次执行
调用栈是一种栈结构,它用来存储计算机程序执行时候其活跃子程序的信息
在js里的任务分为两大类:同步任务、异步任务。当遇到 同步任务 时,会直接放到调用栈中交给主线程执行,而当遇到 异步任务 时,会将其放入 enent table 并注册 回调函数,一旦异步任务的触发条件被触发时,event table 会将对应的回调函数放入 任务队列 里,一旦主进程里的任务 执行完毕 后,就会检查任务队列里是否有 可执行的 回调任务,如果有则将其放入调用栈中由主线程执行,而一旦调用栈中所有任务执行完毕,就又会去检查任务队列里是否有可执行的任务,循环往复,形成事件循环
事件循环大体规则如上所述,但在浏览器和node环境下有些许的不同,主要区别在于异步任务,接下来分别讲解下它俩的区别,走你~
浏览器的事件循环
在浏览器器环境下,异步任务的队列分为两种
- 宏任务队列:包括setTimeout、setInterval、网络IO、postMessage
- 微任务队列:包括Promise.then、Object.observe、MutationObserver
浏览器为了保证页面性能,会在 不同宏任务 之间执行 页面的渲染,同时会在宏任务执行完成后,去清空宏任务执行期间产生的 所有微任务,即所谓的“清空”微任务队列。任务的执行顺序:同步代码 => 清空微任务队列1 => 页面渲染 => 宏任务1 => 清空微任务队列2 => 页面渲染 => 宏任务2 ………
nodejs的事件循环
在nodejs环境下,异步任务队列分类如下
宏任务:
- timers: 存放setTimeout与setInterval回调
- pending callbacks: 执行操作系统的回调,如udp、tcp
- idle、prepare: 只在系统内部使用
- poll: 存放I/O相关的回调
- check: 存放setImmediate回调
- close callbacks: 存放close事件相关回调
微任务: 包括 process.nextTick、Promise.then
我们需要注意三点:
- 对于宏任务,我们只需要关注timers、poll、和check就好了,其他的都是系统相关的
- 对于微任务,我们需要知道process.nextTick的优先级比Promise.then更高
- 对于timers、poll、和check,优先级为 timers > poll > check
再说下与浏览器环境的不同之处
- nodejs环境下,由于存在多种宏任务类型的队列,因此存在队列切换的情形,切换顺序为 timers => poll => check
- 微任务的执行时机不同,浏览器环境下,微任务是在 每一个 宏任务执行之后就会执行,而nodejs环境下,是需要等到 宏任务队列切换时 才会去执行微任务
- 浏览器环境下,微任务队列是 先进先出 的执行顺序,而在nodejs环境下,由于process.nextTick优先级高于Promise.then,因此微任务队列是 优先级队列,不完全遵从 先进先出 的原则
结语
事件循环机制保证了js以最高的效率进行工作,不会因为一些耗时的异步操作而被阻塞,nodejs也是看中了这个特性才选择js来进行构建,即所谓的“非阻塞的异步I/O模型”,因此事件循环这个点一定要摸透,不然就只能望bug兴叹了~