Event Loop模型理解

2,597 阅读3分钟

简介

事件循环(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

浏览器环境下的事件循环

事件循环模型

  1. 执行script同步代码
  2. 执行栈为空
  3. 从微任务队列(microtask queue)中取出队首的回调任务,放入调用栈中执行
  4. 继续取出微任务队列队首的任务,放入调用栈中执行,以此类推,直到直到把微任务队列中的所有任务都执行完毕
  5. 取出宏任务队列(macrotask queue)中位于队首的任务,放入调用栈中执行
  6. 重复2-5步骤

流程图大致如下:

浏览器中event loop流程图

Nodejs中的事件循环

在node中,事件循环表现出的状态与浏览器中大致相同。不同的是node中有一套自己的模型。node中事件循环的实现是依靠的libuv引擎。我们知道node选择chrome v8引擎作为js解释器,v8引擎将js代码分析后去调用对应的node api,而这些api最后则由libuv引擎驱动,执行对应的任务,并把不同的事件放在不同的队列中等待主线程执行。 因此实际上node中的事件循环存在于libuv引擎中。

libuv的结构图

事件循环模型

NodeJS的Event Loop中,执行宏队列的回调任务有6个阶段,如下图:

nodejs中事件循环模型

各个阶段执行的任务如下:

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 队列

参考文章

一次弄懂Event Loop(彻底解决此类面试问题)