生命周期图例

image.png

简单介绍

node的生命周期类似es6的时间循环但是js是单线程,而node是多线程,而且node跟js一样有宏任务与微任务
微任务:

  • nextTick
  • Promise

宏任务

  • timers
  • poll
  • check
  • 图中那一圈都是宏任务

    mian

    启动入口文件,把全局环境代码运行

    eventloop?

    是否进入事件循环, 他会检查别的线程中是不是还有其他事情还没有处理或者还有其他任务还没有完成,
    事件循环运行的过程就是走一圈回来看有没有结束,如果没有结束再走一圈,当走完一圈会到eventloop?发现结束啦(也即是没有任何任务等待啦)就会走到over
    image.png

    示例

  1. setTimeout(() => {
  2. console.log('setTimout');
  3. }, 5000);
  4. console.log('hello');

结果

image.png

解析

先走入main 也就是将入口文件中全局环境代码运行起来也就是执行下面这段代码

  1. console.log('hello');

而后将settimout的代码交给事件调度线程,也就是交给啦时间记时线程,五秒中后将推向事件队列,由于还在记时执行到eventloop?时还不能结束,还用任务未完成,故会等待,等到没有任何任务可做啦才会结束
每一次的事件循环叫做一次循环或者一次轮训 一次循环要经历六个阶段也就是圆圈上的六个圆圈

timers:存放计时器的回调函数

setTimeout 和 setInterval 的回调函数在此队列中,其实也timers也不是队列,而是多个时间线程,它会查看所有时间线程,看那个满足要求执行

poll:轮询队列

先看图
image.png
poll的运行方式很特别当运行到该阶段时如果有poll中有回调,会依次执行回调,直到队列清空,
当poll中没有回调:
如果其他队列中出现回调,会结束该阶段,进入下一阶段,如果后续阶段触发不了回调,会一直到event loop?然后再开启一次新的循环去执行该回调
当其他队列也没有回调,会持续等待,直到出现回调为止。

示例1

  1. setTimeout(() => {
  2. console.log('setTimout');
  3. }, 5000);
  4. console.log('hello');

讲解

还是这段代码
上面我们知道setTimeout的回调会交给事件调度线程,也就是timers线程,也就是main阶段执行完,到啦event loop?时 事件队列里有任务,会进入一次循环,而到啦timers阶段时它会查看所有时间线程,看那个时间线程满足触发条件,而此时肯定还不到5000毫秒,故会进入下一段,直到poll而poll阶段中它本身对列中并没有回调函数,而其他队列也没有回调,会在这一直等待,而到啦setTimeout回调触发的时间,timers队列中出会出现回调,而此时还停留在poll阶段,但是其他阶段出现回调,会结束该阶段,进入下一阶段,直到一次循环结束event loop?中还有任务没有完成会再次开启一次循环,而到timers阶段会执行setTimeout的回调函数,而后又会到Poll阶段已经没有任何任务啦,它就是结束啦,这次循环结束后event loop?发现没有任何任务啦,进入over阶段

示例2

  1. setTimeout(function f1() {
  2. console.log('setTimout');
  3. }, 5000);
  4. const http = require('http');
  5. const server = http.createServer((req, res) => {
  6. console.log('request in');
  7. });
  8. server.listen(9527);

运行结果

image.png
毫无疑问程序卡在这,因为没有客户端连接

讲解

它的运行顺序,这里的顺序会直接从event pool?出开始讲解,当进入eventpool?时事件队列中有两个任务,会进入第一次循环,到啦timers阶段时,由于回调函数f1的触发时间未到,故向下阶段运行,直到pool阶段,可是本应该在pool阶段触发的函数由于此时没有客户端连接进来故不会触发,也就是pool阶段并没有回调,而此时其他阶段也没有回调函数,故在此等待,当回调函数f1满足触发条件,pool阶段结束,但是到啦event pool?时 事件队列里还有任务,此时会进入第二次循环,到啦timer阶段将回调函数f1执行,执行完成后进入下阶段,直到pool,而此时还时没有客户端连接进来,故会再次等待下次回调,但是如果等待的时间过长,会强行结束该阶段进入下阶段,但是eventpool?中还有任务会开启第三次循环,还是会在pool阶段等待客户端连接服务器来触发回调函数

示例3

  1. const start = Date.now();
  2. setTimeout(function f1() {
  3. console.log("setTimeout", Date.now() - start);
  4. }, 200);
  5. const fs = require("fs");
  6. fs.readFile("./index.js", "utf-8", function f2() {
  7. console.log("readFile");
  8. const start = Date.now();
  9. while (Date.now() - start < 300) {}
  10. });

运行结果

image.png

讲解

这个例子也是直接从event loop开始讲起,到达event loop?阶段,此时事件队列中会有两个任务一个是setTimeout的回调函数f1一个是文件读取的回调函数f2 ,这时就会开启第一次循环,到啦timers时并没有到回调函数发f1的触发条件,直接进入下一阶段,直到pool,此时读取可能还没有读取完成,因为读取文件也是需要时间的,pool没有回调,其他阶段也没有回到,那马pool就等下一个回调任务出现,而此时,文件读取成功,pool阶段开始执行f2函数,但是此时的f1函数也满足触发条件,pool由于还在忙碌中,故会让其等待,因为分f2函数会300毫秒后结束,故等到300毫秒后f2函数结束pool阶段的队列已经空啦,而此时还有其他回调任务,pool阶段结束,直到event pool?由于还有任务开启第二次循环,直到timers阶段执行f1函数,f1函数执行完后,timers阶段结束,直到pool阶段,由于没有任务回调任务,会直接结束pool阶段直到event pool?此时事件队列为空,故进入over阶段

check:检查阶段

使用setImmediate的回调会直接进入这个队列

setImmediate 与setTimeout的区别

  1. setTimeout(() => {
  2. console.log("setTimeout");
  3. }, 0);
  4. // 上面代码相当于下面的代码
  5. setImmediate(() => {
  6. console.log("setImmediate");
  7. });

timers并不是真正意义上的队列,只是为啦好记,timers阶段会将时间线程拿出来遍历看看那个满足触发条件,check是真队列,它直接将setImmediate的回调函数添加进队列,

区别

效率上

  1. let i = 0;
  2. console.time();
  3. function test() {
  4. i++;
  5. if (i < 1000) {
  6. // setImmediate(test);
  7. setTimeout(() => {
  8. test()
  9. }, 0);
  10. } else {
  11. console.timeEnd();
  12. }
  13. }
  14. test();

setTimeout 执行时间
image.pngsetImmediate执行时间
image.pngsetImmediate的执行效率高于setTimeout ,因为setImmediate时在对队列中,到啦check阶段,就会让setImmediate的回调函数按照顺序执行而setTimeout 在timers阶段,会将所有时间线程遍历一遍看那个满足条件

示例1:

  1. setTimeout(() => {
  2. console.log("setTimeout");
  3. }, 0);
  4. setImmediate(() => {
  5. console.log("setImmediate");
  6. });

这段程序那个先执行,
这个是由争议的 因为setTimeout的虽然时0秒执行但是没有不可能达到0秒的,至少是1秒,如果程序在1秒之前跑完,这时timers中的回调还没达到触发条件,那吗setImmediate就会触发,但是如果刚好是setTimeout先触发呢,这就不太确定啦。

示例2:

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

当在这种情况下就不会出现示例1的那种情况,因为当文件读取的回调函数触发后,下一个阶段是check 肯定会先执行setImmediate的回调函数后,直到eventpool?,在开启第二次循环才会执行setTimeout的回调函数

nextTick与Promise

它们两个并不属于事件循环的一部分,也不会开启额外的线程。他俩属于微任务
它们是有优先级的,nextTicek的优先级比Promise高优先执行nextTick
当进入事件循环时会优先执行微任务,也就是优先执行nextTick之后执行Promise才会去执行事件循环也就是:
事件循环中,每次打算执行一个回调之前,必须要先清空nextTick和promise队列

示例1

  1. setImmediate(() => {
  2. console.log(1);
  3. });
  4. process.nextTick(() => {
  5. console.log(2);
  6. process.nextTick(() => {
  7. console.log(6);
  8. });
  9. });
  10. console.log(3);
  11. Promise.resolve().then(() => {
  12. console.log(4);
  13. process.nextTick(() => {
  14. console.log(5);
  15. });
  16. });

解析

从main开始,执行全局环境的代码,那吗控制台就会打印出3 然后进入eventpool?队列,此处代码中有nextTick 优先执行nextTick,那吗控制台打印的就是2 和 3 之后执行Promise,控制台打印出4 和5 最后只剩setImmediate 那吗控制台打印 1
打印结果就是 3 2 6 4 5 1
image.png

示例2

  1. async function async1() {
  2. console.log("async1 start");
  3. await async2();
  4. console.log("async1 end");
  5. }
  6. async function async2() {
  7. console.log("async2");
  8. }
  9. console.log("script start");
  10. setTimeout(function() {
  11. console.log("setTimeout0");
  12. }, 0);
  13. setTimeout(function() {
  14. console.log("setTimeout3");
  15. }, 3);
  16. setImmediate(() => console.log("setImmediate"));
  17. process.nextTick(() => console.log("nextTick"));
  18. async1();
  19. new Promise(function(resolve) {
  20. console.log("promise1");
  21. resolve();
  22. console.log("promise2");
  23. }).then(function() {
  24. console.log("promise3");
  25. });
  26. console.log("script end");

解析

同步代码:
script start
async1 start
async2
promise1
promise2
script end
nextTick:
nextTick
Promise:
async1 end
promise3
setTimeout:
0~setTimeout0
3~setTimeout3
setImmediate:
setImmediate
从setTimeout与setImmediate就有一点争议啦,有可能程序运行的快跳过setTimeout先执行setImmediate,但是在我电脑运行的是先把setTimeout执行完才执行的setImmediate
image.png