第108题 浏览器和nodejs事件循环(Event Loop)有什么区别

单线程和异步

  • JS是单线程的,无论在浏览器还是在nodejs
  • 浏览器中JS执行和DOM渲染共用一个线程,是互斥的
  • 异步是单线程的解决方案

浏览器中的事件循环

异步里面分宏任务和微任务

  • 宏任务:setTimeoutsetIntervalsetImmediateI/OUI渲染,网络请求
  • 微任务:Promiseprocess.nextTickMutationObserverasync/await
  • 宏任务和微任务的区别:微任务的优先级高于宏任务,微任务会在当前宏任务执行完毕后立即执行,而宏任务会在下一个事件循环中执行
    • 宏任务在页面渲染之后执行
    • 微任务在页面渲染之前执行
    • 也就是微任务在下一轮DOM渲染之前执行,宏任务在DOM渲染之后执行

    console.log('start')
    setTimeout(() => { 
      console.log('timeout')
    })
    Promise.resolve().then(() => {
      console.log('promise then')
    })
    console.log('end')
    
    // 输出
    // start 
    // end 
    // promise then
    // timeout
    // 分析
    
    // 等同步代码执行完后,先从微任务队列中获取(微任务队列优先级高),队列先进先出
    
    // 宏任务 MarcoTask 队列
    // 如setTimeout 1000ms到1000ms后才会放到队列中
    const MarcoTaskQueue = [
      () => {
        console.log('timeout')
      },
      fn // ajax回调放到宏任务队列中等待
    ]  
    
    ajax(url, fn) // ajax 宏任务 如执行需要300ms
    
    
    // ********** 宏任务和微任务中间隔着 【DOM 渲染】 ****************
    
    // 微任务 MicroTask 队列
    const MicroTaskQueue = [
      () => {
        console.log('promise then')
      }
    ]
    
    // 等宏任务和微任务执行完后 Event Loop 继续监听(一旦有任务到了宏任务微任务队列就会立马拿过来执行)...
    <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) // 2
    
      console.log('start')
      // 宏任务在页面渲染之后执行
      setTimeout(() => {
        const list = document.getElementsByTagName('p')
        console.log('length on timeout----', list.length) // 2
        alert('阻塞 timeout') // 阻塞JS执行和渲染
      })
      // 微任务在页面渲染之前执行
      Promise.resolve().then(() => {
        const list = document.getElementsByTagName('p')
        console.log('length on promise.then----', list.length) // 2
        alert('阻塞 promise') // 阻塞JS执行和渲染
      })
      console.log('end')
    </script>

nodejs中的事件循环

  • nodejs也是单线程,也需要异步
  • 异步任务也分为:宏任务 + 微任务
  • 但是,它的宏任务和微任务分为不同的类型,有不同的优先级
  • 和浏览器的主要区别就是类型优先级,理解了这里就理解了nodejs的事件循环

宏任务类型和优先级

类型分为6个,优先级从高到底执行

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

注意process.nextTick优先级最高,setTimeoutsetImmediate优先级高

执行过程

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

总结

  • 浏览器和nodejs的事件循环流程基本相同
  • nodejs宏任务和微任务分类型,有优先级。浏览器里面的宏任务和微任务是没有类型和优先级的
  • node17之后推荐使用setImmediate代替process.nextTick(如果使用process.nextTick执行复杂任务导致后面的卡顿就得不偿失了,尽量使用低优先级的api去执行异步)
    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')
    
    // 输出
    // start
    // end
    // nextTick
    // promise then
    // timeout
    // setImmediate
Last Updated:
Contributors: leeguooooo