node学习—深入剖析node生命周期(事件循环)

深入剖析node生命周期(事件循环)


要想学好本篇博文,需要先了解js的事件循环: js事件循环原理.

一、node生命周期

提一嘴:node底层使用C语言libuv库操控操作系统
在这里插入图片描述
由图,从运行模块入口main.js后,event loop会检测整个环境是否还有事件未结束,如文件读写等,定时器等。当所有事件全部执行完毕时,才会结束运行。

其中每一个阶段都会维护一个事件队列,这里主讲其中三个。

1.timers

timers:存放计时器的回调函数。
timers内部相当于一个循环在不停侦听定时器是否到达指定执行选时间。

setTimeout(()=>{
    console.log("1s后输出");
},1000);
console.log('abcd');

此时timers将()=>{console.log("1s后输出")}回调函数执行后即清空了它的队列。

2.poll

poll:轮询队列,绝大部分回调都会放入该队列,比如:文件的读取、监听用户请求。
在这里插入图片描述

setTimeout(function f1(){
    console.log("1s后输出");
},1000);
console.log('abcd');

还是这段代码,f1要等1s的事件,所以timers里没有回调函数,然后到poll轮训队列,不停地查看是否有回调,1s后f1出现了,将f1放入timers队列,下一步check也没有。再来到timers,执行f1,清空了timers队列。然后又poll轮询,最终没有任何回调了,over。

setTimeout(function f1(){
    console.log("1s后输出");
},1000);
const http = require("http");

const server = http.creareServer((req,res)=>{
    console.log("request in");
});
server.listen(9527);

到达event loop事件循环,查看是否有值得等待的事件。此时timers还没有回调函数,然后到了poll轮询队列,不停地查看是否有回调,此时f1出现了,将f1放入timers队列,下一步check也没有。再来到timers,执行f1,清空了timers队列。又到了poll轮询,又一直等待用户请求触发createServer回调,等待很长时间仍未触发,则进入下一步check,然后又timers,poll…知道用户出发该回调函数后才能停止循环over。
接下来看这一段代码

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)=>{
    console.log("readFile");
    const start = Date.now();
    while (Date.now() - start < 300){}
});

在这里插入图片描述
此时问题来了,怎么setTimeout等待了509ms呢
在这里插入图片描述
再看看这张图:
到达event loop事件循环,此时timers中没有回调。到poll轮询,此时发现需要读取文件,等待文件读取,并继续轮询,(如果文件较小)此时文件读取完成,触发了回调f2,将f2放入poll队列。执行f2,输出readFile,准备等待300ms后结束f2。等待期间此时f1好了,将f1放入timers队列,但是f2在执行中,所以继续执行f2,300ms后f2结束。发现此时没有任何需要等待的事。下一步check,没有事。又循环到timers,执行f1清空timers队列,接着poll发现没有任何事了,下一步,check也没有,最后over。所以这里显示setTimeout最终等待了509ms。

3.check

check:检查阶段,使用setImmediate的回调会直接进入这个队列
setImmediate相当于setTimeout(()=>{},0),一旦发现有setImmediate就会直接放入check队列。效率比timers高很多。

let i = 0;
console.time();
function test(){
    i++;
    if(i < 100){
        setTimeout(test,0);
    }else{
        console.timeEnd();
    }
}
test();

在这里插入图片描述

let i = 0;
console.time();
function test(){
    i++;
    if(i < 100){
        setImmediate(test);
    }else{
        console.timeEnd();
    }
}
test();

在这里插入图片描述
因为setTimeout每次需要判断时间放入timers队列,setImmediate没有计时直接放入check队列,所以setTimeout效率远远低于setImmediate。
但是setImmediate(()=>{})setTimeout(()=>{},0)的执行顺序就不一定了,得看计算机当时的执行环境和速度,且setTimeout一般取不到0,至少都是1

const fs = require("fs");
fs.readFile("./index.js",()=>{
    setTimeout(()=>console.log(1));
    setImmediate(()=>console.log(2));
});

从主线程到event loop事件循环,此时timers没有回调事件,接着到poll轮询,等待文件读取完成,执行回调函数,将setTimeout放入timers,setImmediate放入check。下一步check执行setImmediate。接着循环,timers执行setTimeout,poll轮询不到任何回调事件,over。所以这种情况下,setImmediate必先执行与setTimeout之前。
在这里插入图片描述
此时回想js的事件循环,讲到了宏任务和微任务,那么在node中是怎么回事呢?
在这里插入图片描述
所以我们前面讲到的事件循环相当于宏任务,而nextTick和Promise相当于微任务。其实它们不是事件循环的一部分,node希望优先用最快的速度去执行它们,其中nextTick优先度最高。
在这里插入图片描述
看下面这一面试题,答案就显而易见了

setTimeout(() => {
    console.log(1);
});
process.nextTick(()=>{
    console.log(2);
    process.nextTick(()=>{
        console.log(6);
    });
});
console.log(3);
Promise.resolve().then(()=>{
    console.log(4);
    process.nextTick(()=>{
        console.log(5)
    });
});

在这里插入图片描述
看下面这一道面试题

async function async1() {
    console.log("async1 start");
    await async2();
    console.log("async1 end");
}
async function async2() {
    console.log("async2");
}
console.log("script start");
setTimeout(function () {
    console.log("setTimeout0");
}, 0);
setTimeout(function () {
    console.log("setTimeout3");
}, 3);
setImmediate(() => console.log("setImmediate"));
process.nextTick(() => console.log("nextTick"));
async1();
new Promise(function (resolve) {
    console.log("promise1");
    resolve();
    console.log("promise2");
}).then(function () {
    console.log("promise3");
});
console.log("script end");

我们根据代码顺序一步一步来:

  1. console.log("script start");
  2. timers:0ms_setTimeout0;3ms_setTimeout3
  3. check: setImmediate
  4. nextTick: console.log("nextTick"));
  5. console.log("async1 start");
  6. console.log("async2");
  7. Promise:console.log("async1 end");
  8. console.log("promise1");
  9. console.log("promise2");
  10. Promise:console.log("async1 end"); console.log("promise3");
  11. console.log("script end");

所以我们得出同步的结果:

  1. console.log("script start");
  2. console.log("async1 start"); 此时await async2();后为等待,即为Promise队列,所以console.log("async1 end");放入Promise队列
  3. console.log("async2");
  4. console.log("promise1");
  5. console.log("promise2");
  6. console.log("script end");

接下来我们查看队列:

  1. console.log("nextTick");
  2. console.log("async1 end")

但是后面就有问题了

setTimeout(function () {
    console.log("setTimeout0");
}, 0);
setTimeout(function () {
    console.log("setTimeout3");
}, 3);
setImmediate(() => console.log("setImmediate"));

我们不能确定得到同步的结果时是否消耗了3ms,所以不能timers队列里是否有setTimeout0或setTimeout3,最终不能确定setImmediate的实行顺序。
在这里插入图片描述
在作者电脑里运行时,得到同步结果时显然已经超过了3ms,所以第一次事件循环时timers队列里已经有setTimeout0和setTimeout3了。

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞羽逐星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值