浏览器和nodejs的事件循环有什么区别?

42 阅读2分钟

单线程和异步

  • JS是单线程的(无论在浏览器还是nodejs)
  • 浏览器中JS执行和DOM渲染同一个线程
  • 异步

宏任务和微任务

  • 宏任务,如setTimeout setInterval 网络请求
  • 微任务,如promise async/await
  • 微任务在下一轮DOM渲染之前执行,宏任务在之后执行

代码演示

console.log('start');
setTimeout(()=>{
  console.log('timeout');
})
Promise.resolve().then(()=>{
  console.log('promise then');
})
console.log('end');

image.png

  1. 先start 后end 执行同步任务
  2. 后微任务Promise
  3. 后宏任务setTimeout

演示代码

  <body>
    <p>event-loop</p>
    <script>
      const p = document.createElement('p')
      p.innerHTML = 'new paragraph'
      document.body.appendChild(p)
      const list = document.getElementsByTagName('p')
      console.log('length----', list.length)

      console.log('start')
      // 渲染之后
      setTimeout(() => {
        const list = document.getElementsByTagName('p')
        console.log('length on timeout', list.length)
        alert('阻塞 timeout')
      })
      // 渲染之前
      Promise.resolve().then(() => {
        const list = document.getElementsByTagName('p')
        console.log('length on promise.then ----', list.length)
        alert('阻塞 promise')
      })
      console.log('end')
    </script>
  </body>

结果:

image.png

点击确定后👇

image.png

  • 宏任务和微任务都不会干扰JS对DOM的操作!

浏览器 event loop

image.png

代码演示

  console.log('start')
  setTimeout(() => {
    console.log('timeout')
  })
  Promise.resolve().then(() => {
    console.log('promise then')
  })
  console.log('end')
  // 先执行完同步代码,遇到一些异步的API或者函数分别放到宏任务和微任务队列里(不一定立马就放,如ajax或者setTimeout有时间间隔,会到触发点后再放,同步任务执行完后执行微任务、然后宏任务(中间是DOM渲染) 然后 event loop 继续监听
  // 然后👇
  // 宏任务队列 MarcoTask Queue 队列 
  // () => {
  //   console.log('timeout')
  // }
  // 微任务队列 MicroTask Queue 队列 把微任务队列里的函数执行
  // () => {
  //   console.log('promise then')
  // }
  //执行完后继续监听有没有新的任务 进入宏任务/微任务队列

先执行完同步代码,遇到一些异步的API或者函数分别放到宏任务和微任务队列里(不一定立马就放,如ajax或者setTimeout有时间间隔,会到触发点后再放,同步任务执行完后执行微任务、然后宏任务(中间是DOM渲染) 然后 event loop 继续监听

Nodejs 异步

  • Nodejs 同样使用ES语法,也是单线程,也需要异步
  • 异步也分为:宏任务 + 微任务
  • 但是,它的宏任务和微任务,分不同类型,有不同优先级

代码演示

console.info('start');
setImmediate(() => {
  console.info('setImmediate');
})
setTimeout(() => {
  console.info('timeout');
})
Promise.resolve().then(() => {
  console.info('promise then');
})
process.nextTick(() => {
  console.info('nextTick');
})
console.info('end')

结果

image.png

nodejs 宏任务类型和优先级

  • Timers - setTimeout setInterval
  • I/O callbacks - 处理网络、流、TCP的错误回调
  • Idle, prepare - 闲置状态(nodejs 内部使用)
  • Poll 轮询 - 执行poll 中的I/O 队列
  • Check 检查 - 存储 setImmediate 回调
  • close Callback - 关闭回调,如socket.on('close')

nodejs 微任务类型和优先级

  • 包括:promise,async/await,process.nextTick
  • 注意, process.nextTick 优先级最高

nodejs event loop

  • 执行同步代码
  • 执行微任务(process.nextTick 优先级更高)
  • 按顺序执行6个类型的宏任务(每个任务开始之前都执行当前的微任务)

image.png

答案

  • 浏览器和nodejs的event loop 流程基本相同
  • nodejs宏任务和微任务分类型,有优先级
  • 推荐使用setImmediate代替process.nextTick
  • 本文基于nodejs 16版本。nodejs低版本可能会有不同