在前不久的遇到的一道笔试题,问浏览器和node的事件循环机制以及各自的区别。当时就感觉自己好像没有好好研究过node的事件循环,于是本着帮助自己同时帮助他人的初心写下这篇文章,好了废话不多说直接上图。
1. 什么是eventLoop?
它是一个在 JavaScript 引擎等待任务,执行任务和进入休眠状态等待更多任务这几个状态之间转换的无限循环。 我们都知道JavaScript引擎是单线程的,至于为什么是单线程主要是出于JavaScript的使用场景考虑,作为浏览器的脚本语言,js的主要任务是主要是实现用户与浏览器的交互,以及操作dom,如果设计成多线程会增加复杂的同步问题。想象一个场景:多个线程同时操作dom,浏览器渲染引擎该使用哪个线程的结果。当然为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
虽然JS是单线程的,但浏览器却是多线程,其中几个典型的线程已经在图中表示出来,而eventLoop就是沟通JS引擎线程和浏览器线程的桥梁,也是浏览器实现异步非阻塞模型的关键。
2. 接下来详细介绍eventLoop的原理,以及上图中的一些细节。
2.1 宏队列和微队列:
宏队列,macrotask,也叫tasks。 一些异步任务的回调会依次进入macro task queue,等待后续被调用,这些异步任务包括:
- setTimeout
- setInterval
- setImmediate (Node独有)
- requestAnimationFrame (浏览器独有)
- I/O
- UI rendering (浏览器独有)
微队列,microtask,也叫jobs。 另一些异步任务的回调会依次进入micro task queue,等待后续被调用,这些异步任务包括:
- process.nextTick (Node独有)
- Promise
- Object.observe
- MutationObserver
2.2 浏览器运行JS代码的具体流程:
- js引擎将所有代码放入执行栈,并依次弹出并执行,这些任务有的是同步有的是异步(宏任务或微任务)。
- 如果在执行 栈中代码时发现宏任务则交个浏览器相应的线程去处理,浏览器线程在正确的时机(比如定时器最短延迟时间)将宏任务的消息(或称之为回调函数)推入宏任务队列。而宏任务队列中的任务只有执行栈为空时才会执行。
- 如果执行 栈中的代码时发现微任务则推入微任务队列,和宏任务队列一样,微任务队列的任务也在执行栈为空时才会执行,但是微任务始终比宏任务先执行。
- 当执行栈为空时,eventLoop转到微任务队列处,依次弹出首个任务放入执行栈并执行,如果在执行的过程中又有微任务产生则推入队列末尾,这样循环直到微任务队列为空。
- 当执行栈和微任务队列都为空时,eventLoop转到宏任务队列,并取出队首的任务放入执行栈执行。需要注意的是宏任务每次循环只执行一个。
- 重复1-5过程
- ...直到栈和队列都为空时,代码执行结束。引擎休眠等待直至下次任务出现。
2.3 在这个过程中有三个重点:
1. 宏任务每次只取一个,执行之后马上执行微任务。
2. 微任务会依次执行,直到微任务队列为空。
3. 图中没有画UI rendering的节点,因为这个是由浏览器自行判断决定的,但是只要执行UI rendering,它的节点是在执行完所有的microtask之后,下一个macrotask之前,紧跟着执行UI render。