node.js 事件循环

149 阅读2分钟

调用栈、消息队列(任务队列、eventloop)

调用栈 call stack:先进后出

function A() {
  setTimeout(() => {
    console.log(new Date().getTime());
  }, 0);

  B();
}

function B() {
  console.log('b')
}

A()

上述例子中,call stack首先是A函数进入([A]),发现A函数中调用B函数,那么call stack.unshift(B),现在就是[B, A],等待B执行完,就会shift,把B弹出,垃圾回收掉,A执行完,再shift,把A弹出,再次垃圾回收机制回收掉。

消息队列(任务队列、eventloop):分为 微任务队列宏任务队列

 微任务队列
     1. promise 回调
     2. process.nextTick,优先级高于 promise
     3. MutationObserverDOM变化的时候(浏览器环境,非 node 环境)
 宏任务队列
     1. timer(setTimeout, setInterval2. setImmediate,优先级高于 setTimeout
     3. I/O 事件,fs.readFile()
     4. UI 渲染
     
 这里有个事件循环可以注意一下的点(因为我经常会忘了)
 事件循环时,先执行完同步代码,再执行异步代码,其中异步代码,会先执行完所有微任务,然后异步消息队列去宏任
务队列拉取`一个宏任务`执行,若其中有微任务产生,那就再把所有的微任务执行完,再去宏任务队列拉取一个宏任务
执行,以此循环。
async function async1() {
  console.log('async1 start')
  await async2()
  console.log('async1 end');
}

// 本质上等同于上面async1执行时
// new Promise((resolve) => {
//   console.log('async1 start')
//   async2();
//   resolve();
// }).then(() => {
//   console.log('async1 end')
// })

async function async2() {
  console.log('async2')
}

console.log('script start')

setTimeout(function () {
  console.log('setTimeout0')
}, 0)

setTimeout(function () {
  console.log('setTimeout2')
}, 300)

setImmediate(() => console.log('setImmediate'));

process.nextTick(() => console.log('nextTick1'));

async1();

process.nextTick(() => console.log('nextTick2'));

new Promise(function (resolve) {
  console.log('promise1')
  resolve();
  console.log('promise2')
}).then(function () {
  console.log('promise3')
})

console.log('script end')

上述代码就是面试常考的node的事件循环

其结果为:

// 首先是同步代码
console.log('script start')
console.log('async1 start')
console.log('async2') // 在这里,因为await将其异步变为同步代码,所以在调用async2函数后的console.log('async1 end');已变为微任务,放入微任务队列。
console.log('promise1') // new Promise首个函数其实是同步代码
console.log('promise2')
console.log('script end')
// 异步消息队列
// 微任务
console.log('nextTick1'))
console.log('nextTick2'))
console.log('async1 end');
console.log('promise3')
// 宏任务
console.log('setTimeout0') //但是这里其实是与上述宏任务中说的第2. setImmediate,优先级高于 setTimeout;是有冲突的,具体原因看下方描述
console.log('setImmediate')
console.log('setTimeout2')
// 这样的输出结果就很符合逻辑的 setImmediate 优先级高于 setTimeout 了
// 先输出setImmediate
setTimeout(() => {
  console.log("setTimeout");
}, 0);

setImmediate(() => {
  console.log("setImmediate");
});

我这里是这样理解的,node环境中,默认情况下, setTimeout 的延迟时间是1毫秒,即使将延迟设置为0毫秒,实际上也会有1毫秒的延迟。

根据stackoverflow.com中的解释,setTimeout 的延迟参数是指执行回调函数的最小等待时间,而不是确切的时间。

所以如果前面没有别的代码,只有 setTimeout、setImmediate的话,在同一时间执行时,setImmediate是快一步的;

但是如果前面有别的代码,那么其实在执行到settimeout的时候,已经过去了1毫秒了,所以才会直接就输出settimeout。