前端面试必问:Js事件循环机制Eventloop
Js的事件循环机制
JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。整个执行过程,我们称为事件循环过程。一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。
宏任务
Task Queue
常见宏任务:setTimeout、setInterval、setImmediate、I/O、script、UI rendering、用户交互操作、所有的网络请求
微任务
MircoTak Queue
常见微任务:
浏览器:Promise.then/catch/finally、MutationObserver(监视DOM变动的API)、Asyn/Await(实际就是Promise)
Node.js:Process.nextTick
总的结论就是,正常执行主线程中的代码,然后执行宏任务,然后执行该宏任务产生的微任务,若微任务在执行过程中产生了新的微任务,则继续执行微任务,微任务执行完毕后,再回到宏任务中进行下一轮循环。举个例子:
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
// script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout
node 中的事件循环
浏览器中有事件循环,node 中也有,事件循环是 node 处理非阻塞 I/O 操作的机制,node中事件循环的实现是依靠的libuv引擎。由于 node 11 之后,事件循环的一些原理发生了变化,这里就以新的标准去讲,最后再列上变化点让大家了解前因后果。
宏任务和微任务
node 中也有宏任务和微任务,与浏览器中的事件循环类似,其中,
macro-task 大概包括:
- setTimeout
- setInterval
- setImmediate
- script(整体代码)
- I/O 操作等。
micro-task 大概包括:
- process.nextTick(与普通微任务有区别,在微任务队列执行之前执行)
- new Promise().then(回调)等。
阶段概述
- 定时器检测阶段(timers):本阶段执行 timer 的回调,即 setTimeout、setInterval 里面的回调函数。
- I/O事件回调阶段(I/O callbacks):执行延迟到下一个循环迭代的 I/O 回调,即上一轮循环中未被执行的一些I/O回调。
- 闲置阶段(idle, prepare):仅系统内部使用。
- 轮询阶段(poll):检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞。
- 检查阶段(check):setImmediate() 回调函数在这里执行
- 关闭事件回调阶段(close callback):一些关闭的回调函数,如:socket.on('close', ...)。
node 和 浏览器 eventLoop的主要区别
两者最主要的区别在于浏览器中的微任务是在每个相应的宏任务中执行的,而nodejs中的微任务是在不同阶段之间执行的。