一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第16天,点击查看活动详情。
node 生命周期
程序运行时首先运行入口文件检查是否进入事件循环,检查其他线程是否还有任务未处理结束,然后一直在事件模型中循环。等所有任务完成之后结束程序。
在事件模型中执行的顺序:timer、pendingcallback、idleprepare、poll、check、closecallbacks。
- timer:存放计时器的回调函数。
- poll:轮询队列。除了timers、checks绝大部分回调都会存入该队列,如:文件内容的读取,监听用户请求。
如果poll中有回调,一次执行回调,直到清空队列,如果poll中没有回调等待其他队列出现回调,结束该阶段,进入下一阶段。如果其他队列也没有回调,持续等待直到出现回调为止。在 node 程序中等待事件最长的就是在poll阶段。
const start = Date.now();
setTimeout(function f1(){
console.log("setTimeout", Date.now() - start);
}, 200)
const fs = require("fs");
fs.readFile("./index.js", "utf-8", (err, data)=>{
const start = Date.new();
while(Date.now() - start < 300){}
})
通过输出发现,setTimeout 真实的时间是500多毫秒。分析 node 的运行情况来寻找答案。
首先进入timers队列,初始状态timers队列是空的。然后进入到 poll 阶段,poll 队列也是空的,然后一直卡住,不断轮询。在文件读取完成之后,poll队列清空,进入下一阶段。event loop 在 timers 队列有任务 f1 ,再执行此时的时间已经明显超过了预定的时间。执行完成之后,程序结束。
- check:检擦阶段,使用setImmediate 的回调会直接进入该队列。
我们可以把 setImmediate 近似的认为 setTimeout 延迟时间为 0。但是setImmediate 效率跟高。
setTimeout(()=>{
console.log("setTimeout")
},0)
setImmediate(()=>{
console.log("setImmediate")
})
每次输出顺序都不一定,其实timeout的时间必须是有一个延迟的无限趋近0,当时程序的运行快的话 timeout 已经推入了队列,取决于计算机的运行环境。
每个阶段会维护一个事件队列。在到达一个队列的时候,会查看队列是否存在任务,清空后再进行下一阶段。
- nextTick、Promise
如果用宏任务和微任务区分的话,上面的都是宏任务,这两个是微任务。 nextTick优先级要比Promise高。 事件循环中,每次打算执行一个回调之前,必须先清空nextTick和Promise队列。
结语
到此关于 NodeJS 的知识点已经总结完毕,如果有需要补充或错误的地方还请多多指教。