从一道执行题,了解Node中JS执行机制

与浏览器环境有何不一样

node环境和浏览器环境,表现出来的事件循环状态,大致表现一致 惟一不一样的是:javascript

  1. JS引擎存在 monitoring process 进程,会持续不断的检查主线程执行为空,一旦为空,就会去 callback queue 中检查是否有等待被调用的函数。(只有宏任务和微任务两个队列)
  2. node 中是依靠 libuv 引擎实现,咱们书写的 js 代码有 V8 引擎分析后去调用对应的 nodeAPI ,这些 api 最后由 libuv 引擎驱动,在 libuv 引擎中有一套本身的模型,把不一样的事件放在不一样的队列中等待主线程执行。( 模型中有6种宏任务队列和1种微任务队列 )

Node事件循环的几个阶段

// libuv引擎中的事件模型,在每一个模型后面都添加了一些说明

   ┌───────────────────────────────────────────────────────┐
┌─>│        timers       │ setTimeout/setInterval的回调
│  └──────────┬────────────────────────────────────────────┘
│             ↓
│  ┌──────────┴────────────────────────────────────────────┐
│  │   pending callbacks │ 处理网络、流、tcp的错误回调
│  └──────────┬────────────────────────────────────────────┘
│             ↓
│  ┌──────────┴────────────────────────────────────────────┐
│  │     idle, prepare   │ 只在node内部使用
│  └──────────┬────────────────────────────────────────────┘
│             ↓                                                      ┌───────────────┐
│  ┌──────────┴────────────────────────────────────────────┐         │   incoming:   │
│  │         poll        │ 执行poll中的i/o队列,检查定时器是否到时  <------│   connections,
│  └──────────┬────────────────────────────────────────────┘         │   data, etc.  │
│             ↓                                                      └───────────────┘
│  ┌──────────┴────────────────────────────────────────────┐
│  │        check        │ 存放setImmediate回调
│  └──────────┬────────────────────────────────────────────┘
│             ↓
│  ┌──────────┴────────────────────────────────────────────┐
└──┤    close callbacks  │ 关闭的回调(socket.on('close')...)
   └───────────────────────────────────────────────────────┘
复制代码

Node事件循环中的几个阶段

官方的event-loop-timers-and-nexttick更详细的说明java

  1. times:这个阶段执行定时器队列中的回调函数 ( setTimeoutsetInterval )
  2. pending callback:这个阶段执行几乎全部的回调( 网络、流、tcp错误... )。除了,close 回调、定时器回调、setImmediate 回调这3个规定好的阶段
  3. idle,prepare:这个阶段仅在内部使用( 能够暂不理会 )
  4. poll:等待新的I/O事件,node在特殊状况下会阻塞这里,检查定时器是否到时( 入口 )
  5. checksetImmediate() 的回调会在这个阶段执行
  6. close callbacks:例如 socket.on('close', ...)
  7. process.nextTick.then() 会在事件循环的阶段切换过程当中执行

说了一堆概念,来一块儿看看下面这段代码

(function test() {
  setTimeout(function () { console.log(4) }, 0);
  new Promise(function (resolve, reject) {
    console.log(1);
    for (var i = 0; i < 10000; i++) {
      i == 9999 && resolve();
    }
    console.log(2);
  }).then(function () {
    console.log(5);
  });
  console.log(3);
})();
// 这段代码是否是很熟悉
// 最终结果1,2,3,5,4 和 浏览器中效果一致
复制代码

来点稍微高难度的

和上篇博客 从一道执行题,了解浏览器中JS执行机制 中的代码同样 (⊙﹏⊙)bnode

console.log(1)

setTimeout(() => {
  console.log(2)
  new Promise(resolve => {
    console.log(4)
    resolve()
  }).then(() => {
    console.log(5)
  })
})

new Promise(resolve => {
  console.log(7)
  resolve()
}).then(() => {
  console.log(8)
})

setTimeout(() => {
  console.log(9)
  new Promise(resolve => {
    console.log(11)
    resolve()
  }).then(() => {
    console.log(12)
  })
})
// 浏览器中的结果:一、七、八、二、4 , 五、九、十一、12
// Node 中的结果:一、七、八、二、4 , 九、十一、五、12
复制代码

解析以下:面试

  1. 在浏览器中 macro task 执行完成后,再次循环 宏任务 的回调队列以前,会优先处理micro中的任务。所以结果是: 一、七、八、二、四、五、九、十一、12
  2. Node 中有6个宏任务队列,事件循环首先进入 poll 阶段。进入 poll 阶段后查看是否有设定的 timers ( 定时器 )时间到达,若是有一个或多个时间到达, Event Loop 将会跳过正常的循环流程,直接从 timers 阶段执行,并执行 timers 回调队列,此时只有把 timers 阶段的回调队列执行完毕后。才会走下一个阶段,这也就是为何 setTimeout 中有 .then,而没有被当即执行的缘由,当 timers 阶段的回调队列执行完毕后,切换到下一个阶段这个过程当中去触发 微任务(process.nextTick.then) 。在阶段与阶段的切换之间。

再来一道基础题

setTimeout(function () {
  console.log('setTimeout')
});
setImmediate(function () {
  console.log('setImmediate')
});
复制代码

执行结果:( setTimeout、setImmediate ) 或 ( setImmediate、setTimeout )api

为何? setTimeout 在标准中默认的最小时间是4ms,若是开启node和执行node代码的时间小于4ms,那么代码解析完成后传入 libuv 引擎,首先会进入 poll 阶段,此时查看设定的时间是否达到截止时间点,若是这个时间小于4ms( 没有达到 ),那么会走 check 阶段,会触发 setImmediate 再触发 setTimeout。若是开启node和执行node代码时间大于等于4ms,那么就会先执行 setTimeout 后执行 setImmediate浏览器

在基础上进化的经典题

setImmediate(() => {
  console.log('setImmediate1')
  setTimeout(() => {
    console.log('setTimeout1')
  }, 0);
})
setTimeout(()=>{
  process.nextTick(()=>console.log('nextTick'))
  console.log('setTimeout2')
  setImmediate(()=>{
    console.log('setImmediate2')
  })
},0);
复制代码

两种状况 ( nextTick执行的位置:是在队列切换时执行 )网络

  1. 若是 setImmediate 先执行:setImmediate一、setTimeout二、setTimeout一、nextTick、setImmediate2
  2. 若是 setTimeout 先执行:setTimeout二、nextTick、setImmediate一、setImmediate二、setTimeout1

setImmediate和process.nextTick的直译

  1. Immediate当即执行的意思,其其实是固定在 check 阶段才会被执行。这个直译的意义和 process.nextTick 才是最匹配的。
  2. node的开发者们也清楚这两个方法的命名上存在必定的混淆,他们表示不会把这两个方法的名字调换过来---由于有大量的node程序使用着这两个方法,调换命名所带来的好处与它的影响相比不值一提。

了解这些东西有什么用?

  1. 可使咱们对异步代码的执行顺序有清晰的认知( 重要的 )
  2. 推迟任务执行
  3. 面试

总结

这些概念远比想象中的要重要异步

  1. 为何 new Promise 第一个参数是同步执行的 ?学习Promise && 简易实现Promise
  2. 浏览器 中的 JS 执行机制是什么样子的?从一道执行题,了解浏览器中JS执行机制

附:这篇博客 也许 想表达 概念远比想象中的要重要 (⊙﹏⊙)bsocket

相关文章
相关标签/搜索