Node 11+ 是与浏览器的执行结果一致

NodeJS 系统

  1. 通过事件循环机制运行 JS 代码
  2. 提供线程池处理 I/O 操作任务
  3. 两种线程:
    1. 事件循环线程:负责任务安排
      (require,同步执行回调、注册新任务)
    2. 线程池(libuv 实现):负责处理任务
      (I/O 操作、CPU 密集型任务)

image.png


image.png
事件队列实际上不存在维护事件队列的东西出现,当拿到事件时就住相应 API 线程池中扔。

Node 事件环

Node 事件环 与 浏览器事件环不一样,
浏览器事件环可以明显感觉为一个环型
image.png
但对于 NodeJS 并不是一个真正的环,是把不同的任务分配至不同的阶段,然后阶段从上往下地重复执行,直至每个阶段都没有任务。因为 NodeJS 的事件环不存在渲染部分,且不存在微任务队列,只有宏任务队列。而宏任务队列就分为 6 个阶段的。
NodeJS 事件环阶段

  1. Timers:setTimeout / setInterval
  2. Pending callbacks:执行延迟到下一个事件环迭代的 I/O 回调(内部机制使用)
  3. Idle,prepare:系统内部机制使用
  4. Poll:检查新的 I/O 事件与执行 I/O 回调
  5. Check:setImmediate
  6. Close callbacks: 关闭的回调函数(内部机制使用)

    案例分析

    ```javascript // promise.then1 Promise.resolve().then(() => { console.log(1); });

// nextTick1 process.nextTick(() => { console.log(2); });

console.log(‘start’);

// readFile1 readFile(‘1.txt’, ‘utf-8’, () => { // setTimeout2 setTimeout(() => { console.log(3); }, 0);

// nextTick2 process.nextTick(() => { console.log(4); });

// setImmediate2 setImmediate(() => { console.log(5); });

console.log(6); });

console.log(7);

// setTimeout1 setTimeout(() => { console.log(8); }, 0);

// setImmediate1 setImmediate(() => { console.log(9); });

console.log(‘end’);

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/1753568/1644213672148-badf0b33-731c-449d-aad0-e80f1f8ea081.png#clientId=u3eaeb3ae-8513-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=206&id=u260bf40d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=206&originWidth=418&originalType=binary&ratio=1&rotation=0&showTitle=false&size=31274&status=done&style=none&taskId=u60f04867-796f-4091-b1e8-2a67bff389b&title=&width=418)

start 7 end

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/1753568/1644212060270-18c83563-4b6e-47d6-808d-8975f2d9823e.png#clientId=u3eaeb3ae-8513-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=196&id=ub8206483&margin=%5Bobject%20Object%5D&name=image.png&originHeight=196&originWidth=415&originalType=binary&ratio=1&rotation=0&showTitle=false&size=23119&status=done&style=none&taskId=uadae88f6-5848-4051-940b-f5e7ec5e6f0&title=&width=415)<br />只要执行阶段在转换之前或之后都要先清空微任务,在微任务中 nextTick 是优先于 promise.then 执行

start 7 end 2 1

  1. 微任务处理完成后,就处理 Node 事件环的任务<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/1753568/1644212531359-ed85933b-f593-47cd-b755-bfef6eaf76e0.png#clientId=u3eaeb3ae-8513-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=196&id=u4ed39a95&margin=%5Bobject%20Object%5D&name=image.png&originHeight=196&originWidth=405&originalType=binary&ratio=1&rotation=0&showTitle=false&size=23026&status=done&style=none&taskId=ua71a2a50-5734-4f98-b2de-f62bb1528d2&title=&width=405)<br />这里 setImmediate1 cb 与 setTimeout1 cb 先后顺序不确定。尽管 setTimeout 的延迟时间为 0,也会有 >= 4 ms 的时间差。如果在 Poll 阶段时已经到 setTimeout1 的时间那么,setTimeout cb 会先进入主执行栈。相反在 Poll 阶段还未到时间,会进入 Check 阶段。把 setImmediate 的 cb 先进入主执行栈,再回到 Poll 阶段,把 setTimeout1 cb 进入主执行栈。

start 7 end 2 1 [ 8 9 ] 或 [ 9 8 ]

![image.png](https://cdn.nlark.com/yuque/0/2022/png/1753568/1644212982078-da7dd480-d6ac-4a9e-a087-150e885b09f4.png#clientId=u3eaeb3ae-8513-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=194&id=uf8c906a8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=194&originWidth=413&originalType=binary&ratio=1&rotation=0&showTitle=false&size=19642&status=done&style=none&taskId=udc65a3fa-0479-4082-be93-d6f1283a9e0&title=&width=413)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/1753568/1644213018700-911d82e9-bd28-4942-b33d-5b2e760545cc.png#clientId=u3eaeb3ae-8513-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=191&id=u6d3e4e46&margin=%5Bobject%20Object%5D&name=image.png&originHeight=191&originWidth=412&originalType=binary&ratio=1&rotation=0&showTitle=false&size=19158&status=done&style=none&taskId=ud35a0386-1a99-4fbc-a892-167c776df9d&title=&width=412)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/1753568/1644213278132-b12f6db2-0351-4e26-8035-bdcfdd816f02.png#clientId=u3eaeb3ae-8513-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=200&id=u08dfc26a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=200&originWidth=415&originalType=binary&ratio=1&rotation=0&showTitle=false&size=21420&status=done&style=none&taskId=u85bdc593-7bb9-4e87-9f54-33a7415ec56&title=&width=415)

start 7 end 2 1 [ 8 9 ] 或 [ 9 8 ] 6 4

![image.png](https://cdn.nlark.com/yuque/0/2022/png/1753568/1644213394202-53141be7-d647-497e-962c-467e61804be4.png#clientId=u3eaeb3ae-8513-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=197&id=u3cefe6e8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=197&originWidth=414&originalType=binary&ratio=1&rotation=0&showTitle=false&size=21733&status=done&style=none&taskId=u5ce72f7c-d615-460a-877b-c4d83440672&title=&width=414)<br />在 I/O 里面一定会先执行 setImmediate 回调

start 7 end 2 1 [ 8 9 ] 或 [ 9 8 ] 6 4 5 3 ```