这是我参与「第四届青训营 」笔记创作活动的第6天
node简介
Node.js采用V8作为js的解析引擎,而I/O处理方面使用libuv,libuv是一个基于事件驱动的跨平台抽象层,封装了不同操作系统一些底层特性,对外提供统一的API,事件循环机制由它实现。
node.js运行机制
- V8引擎解析JavaScript脚本
- 解析后的代码调用Node API
- libuv库负责Node API的执行,它将不同的任务分配给不同的线程,形成一个事件循环,以异步的方式将任务的执行结果返回给V8引擎
- V8引擎将结果返回用户
事件循环
libuv中的事件循环分六个阶段,它们会按照顺序反复运行,每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量达到系统设定的阈值,就会进入下一阶段。
-
timers阶段
执行setTimeout()、setInterval()的回调,由poll阶段控制(与浏览器不同,timers阶段有几个setTimeout、setInterval都会依次执行) -
I/O callbacks阶段:处理上一轮循环中少数未执行的I/O回调
-
idle,prepare阶段:仅node内部使用
-
poll阶段
获取新的I/O事件,适当的条件下node将阻塞在这里。该阶段系统会做两件事:回到timer阶段执行回调;执行I/O回调。在进入该阶段时:如果没有设定timer,则:1.若poll队列不为空,会遍历回调队列并同步执行,直到队列为空或者达到系统限制;2.若poll队列为空,则(1)若有setImmediate回调需要执行,poll阶段会停止并且进入到check阶段执行回调;(2)若没有setImmediate回调需要执行,会等待回调被加入到队列中并立即执行回调,同时会有个超时时间防止死等。
如果设定了timer且poll队列为空,则会判断是否有timer超时,如果有的话会回到timer阶段执行回调
-
check阶段:执行setImmediate()的回调
-
close callbacks阶段:执行socket的close事件回调
宏任务与微任务
MacroTask(宏任务):setTimeout、setInterval、setImmediate、script(整体代码)、I/O操作
MicroTask(微任务):process.nextTick、Promise().then
问题
setTimeout和setImmediate
两者调用时机不同,setImmediate设计在poll阶段完成时执行,即check阶段;setTimeout设计在poll阶段为空闲时,且设定时间达到后在timer阶段执行。二者在异步I/O callbacks内部调用时,总是先执行setImmediate再执行setTimeout;其他情况先后顺序不一定(setTimeout(func, 0)===setTimeout(func, 1),如果在准备时候花费时间大于1ms,则在timers阶段就会直接执行setTimeout回调,小于1ms先执行setImmediate回调)
process.nextTick
独立于Event Loop之外,有一个自己的队列,当每个阶段完成后如果存在nextTick队列,就会转而清空nextTick队列中的所有回调函数,且优先于其他microtask执行
Node与浏览器的Event Loop差异
Microtask任务队列的执行时机不同
- Node端:microtask在事件循环的各个阶段之间执行
- 浏览器端:microtask在事件循环的macrotask执行完之后执行