咱们常常会听到引擎和runtime,它们的区别是什么呢?javascript
require
来引入模块;而在浏览器中,咱们有 window
、 DOM。Js引擎是单线程的,如上图中,它负责维护任务队列,并经过 Event Loop 的机制,按顺序把任务放入栈中执行。而图中的异步处理模块,就是 runtime 提供的,拥有和Js引擎互不干扰的线程。接下来,咱们会细说图中的:栈和任务队列。html
如今,咱们要运行下面这段代码:java
function bar() { console.log(1); } function foo() { console.log(2); far(); } setTimeout(() => { console.log(3) }); foo();
它在栈中的入栈、出栈过程,以下图:node
Js 中,有两类任务队列:宏任务队列(macro tasks)和微任务队列(micro tasks)。宏任务队列能够有多个,微任务队列只有一个。那么什么任务,会分到哪一个队列呢?git
浏览器的 Event Loop 遵循的是 HTML5 标准,而 NodeJs 的 Event Loop 遵循的是 libuv。 区别较大,分开讲。github
咱们上面讲到,当stack空的时候,就会从任务队列中,取任务来执行。浏览器这边,共分3步:web
Event Loop 会无限循环执行上面3步,这就是Event Loop的主要控制逻辑。其中,第3步(更新UI渲染)会根据浏览器的逻辑,决定要不要立刻执行更新。毕竟更新UI成本大,因此,通常都会比较长的时间间隔,执行一次更新。vim
从执行步骤来看,咱们发现微任务,受到了特殊待遇!咱们代码开始执行都是从script(全局任务)开始,因此,一旦咱们的全局任务(属于宏任务)执行完,就立刻执行完整个微任务队列。看个例子:segmentfault
console.log('script start'); // 微任务 Promise.resolve().then(() => { console.log('p 1'); }); // 宏任务 setTimeout(() => { console.log('setTimeout'); }, 0); var s = new Date(); while(new Date() - s < 50); // 阻塞50ms // 微任务 Promise.resolve().then(() => { console.log('p 2'); }); console.log('script ent'); /*** output ***/ // one macro task script start script ent // all micro tasks p 1 p 2 // one macro task again setTimeout
上面之因此加50ms的阻塞,是由于 setTimeout
的 delayTime 最少是 4ms. 为了不认为 setTimeout
是由于4ms的延迟然后面才被执行的,咱们加了50ms阻塞。api
NodeJs 的运行是这样的:
NodeJs 的 Event Loop 分6个阶段执行:
┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ │ └─────────────┬─────────────┘ ┌───────────────┐ │ ┌─────────────┴─────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └─────────────┬─────────────┘ │ data, etc. │ │ ┌─────────────┴─────────────┐ └───────────────┘ │ │ check │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ └───────────────────────────┘
以上的6个阶段,具体处理的任务以下:
setTimeout()
和setInterval()
设定的回调。setImmediate()
设定的回调。socket.on('close', ...)
的回调。每一个阶段执行完毕后,都会执行全部微任务(先 nextTick,后其它),而后再进入下一个阶段。