简介
事件循环(event loop),是JavaScript用来解决由于单线程阻塞造成执行效率低下的机制,也就是我们常说的异步的基石。在不同的JavaScript宿主环境,Event Loop有着不同的模型。
任务队列
任务队列是用来存放异步任务的回调,是一种先进先出的线性结构。由于异步任务之间并不相同,任务队列也由多个队列组成。
宏任务队列(microtask queue)
- setTimeout/setTimeout
- setImmediate
- requestAnimationFrame
- I/O
- UI rendering (浏览器独有)
微任务队列(microtask queue)
- process.nextTick (Node独有)
- Promise
- Object.observe
- MutationObserver
浏览器环境下的事件循环
事件循环模型
- 执行script同步代码
- 执行栈为空
- 从微任务队列(microtask queue)中取出队首的回调任务,放入调用栈中执行
- 继续取出微任务队列队首的任务,放入调用栈中执行,以此类推,直到直到把微任务队列中的所有任务都执行完毕
- 取出宏任务队列(macrotask queue)中位于队首的任务,放入调用栈中执行
- 重复2-5步骤
流程图大致如下:
Nodejs中的事件循环
在node中,事件循环表现出的状态与浏览器中大致相同。不同的是node中有一套自己的模型。node中事件循环的实现是依靠的libuv引擎。我们知道node选择chrome v8引擎作为js解释器,v8引擎将js代码分析后去调用对应的node api,而这些api最后则由libuv引擎驱动,执行对应的任务,并把不同的事件放在不同的队列中等待主线程执行。 因此实际上node中的事件循环存在于libuv引擎中。
事件循环模型
NodeJS的Event Loop中,执行宏队列的回调任务有6个阶段,如下图:
各个阶段执行的任务如下:
timer
timers 阶段会执行 setTimeout 和 setInterval 回调,并且是由 poll 阶段控制的。
I/O
执行除了close事件的callbacks、被timers设定的callbacks、setImmediate()设定的callbacks这些之外的callbacks
idle, prepare
仅node内部使用
poll
poll 是一个至关重要的阶段,这一阶段中,系统会做两件事情
- 回到 timer 阶段执行回调
- 执行 I/O 回调
进入该阶段时如果没有设定了 timer:
- 如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者达到系统限制
- 如果 poll 队列为空,且
setImmediate回调需要执行,poll 阶段会停止并且进入到 check 阶段执行回调 - 如果 poll 队列为空,且
setImmediate回调不需要执行,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去
如果设定了 timer:
- poll 队列为空,则会判断是否有 timer 超时,如果有的话会回到 timer 阶段执行回调
check
check 阶段执行 setImmediate的回调
close callbacks
执行socket.on('close', ....)这些callbacks
对于 microtask 来说,它会在以上每个阶段完成前清空microtask 队列