JS 宏任务与微任务、事件循环、任务队列之间的关系

因为JavaScript是单线程运行的, 也就是说在同一时刻, JavaScript只能执行一个任务, 其他任务只能等待上个任务执行完毕再依次执行, 由此循环往复, 便引出事件循环的概念。

事件循环

事件循环理解比较容易, 如上文所说, JavaScript引擎在 “等待任务”,"执行任务"和"进入休眠状态等待更多任务"这几个状态之间转换的循环便是事件循环

一般执行过程: 当有任务时, 从最先进入的任务开始执行, 如执行完没有其他任务,休眠直到出现任务,然后再从最先进入的任务开始执行。

:事件循环中的事件并不是事件监听中的事件,这里的事件可以简单的理解为就是需要执行任务代码。

任务队列

在一次事件循环中可以有一个或多个任务队列, 一个任务队列便是一系列有序任务的集合,如下代码所示;

  // 以一次性定时器为例
  // 第一个任务队列
  setTimeout(() => {
    console.log(1)
    new Promise(() => {}).then(res => {}) // then 的执行属于微任务
    console.log(2)
    document.querySelector('div').style.color = 'blue' // DOM渲染
  })

  // 第二个任务队列
  setTimeout(() => {
	//......
  })

事件循环的关键步骤如下:

  • 在循环中选择最先进入队列的任务,如示例代码中第一个任务队列中的 console.log(1);
  • 检查此队列中是否存在微任务,如果存在则不停地执行,直至清空微任务队列, 如示例代码中的 new Promise(() => {}).then(res => {});
  • 更新 render(DOM渲染), 如示例代码中 document.querySelector('div').style.color = 'blue';
  • 第一个任务队列执行完成后, 进入第二个任务队列… , 主线程重复执行上述步骤。

在上述循环的基础上需要了解几点:

  • JS分为同步任务(如示例代码中的 console.log() 操作)和 异步任务(如示例代码中的 new Promise(() => {}).then(res => {}) 操作);
  • 同步任务都在主线程上执行, 形成一个执行栈;
  • 异步任务会进入到宿主环境(浏览器便是宿主环境之一)管理的一个任务队列中, 只要异步任务有了运行结果,就在任务队列之中放置一个事件;
  • 一旦执行栈中的所有同步任务执行完毕(如示例代码中的console.log(1)与console.log(2)执行完毕, 此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。

宏任务

可以通俗的理解为每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行), 如示例代码中一个任务队列便是一个宏任务。

常见的宏任务:

任务(代码)宏任务环境
script宏任务浏览器
事件宏任务浏览器
网络请求(Ajax)宏任务浏览器
setTimeout() 定时器宏任务浏览器 / Node
fs.readFile() 读取文件宏任务Node

微任务

微任务可以理解为是宏任务中的一个部分,它的执行时机是在同步代码执行之后,下一个宏任务执行之前。如示例代码中new Promise(() => {}).then(res => {}) then 的执行就属于微任务。


运行机制

最后通过一个示例来对运行机制进行总结

	// 第一个宏任务
	<script>
      // 同步任务1
      console.log(1)

      // 微任务1
      new Promise((resolve, reject) => {
        resolve(8)
      }).then(res => {
        console.log(res)
      })

      function abc() {
        console.log(2)
      }
      // 同步任务2
      abc()

      // 第二个宏任务
      setTimeout(() => {
        // 同步任务4
        console.log(3)

        // 微任务3
        new Promise((resolve, reject) => {
          resolve(7)
        }).then(res => {
          console.log(res)
        })

        // 同步任务5
        console.log(4)
      }, 0)


      // 微任务2
      new Promise((resolve, reject) => {
        resolve(5)
      }).then(res => {
        console.log(res)
      })

      // 同步任务3
      console.log(6)
      
      // DOM渲染
      document.querySelector('div').style.color = 'blue' 

    </script>

代码执行过程如下:

  • 执行一个宏任务(执行栈中没有就从事件队列中获取)

    • 代码从按照上至下的顺序开始执行, 获取到 script 标签(第一个宏任务), 开始执行;

      <script>...</script>
      
    • 将第一个宏任务中的 同步任务(同步任务1、同步任务2、同步任务3)在 执行栈 中, 依次执行;

      一、执行同步任务
      // 同步任务1
      console.log(1) 
      
      function abc() {
        console.log(2)
      }
      // 同步任务2
      abc()
      
      // 同步任务3
      console.log(6)
      
      // 控制台打印 1 2 6
      
  • 执行过程中如果遇到 微任务,就将它添加到 微任务的任务队列

    • 将第一个宏任务中的微任务(微任务1、微任务2)添加到微任务队列, 等待同步任务执行完毕;

      二、将微任务添加在微任务队列中
      // 微任务队列
      // 微任务1
      new Promise((resolve, reject) => {
        resolve(8)
      }).then(res => {
        console.log(res)
      })
      
      // 微任务2
      new Promise((resolve, reject) => {
        resolve(5)
      }).then(res => {
        console.log(res)
      })
      
  • 宏任务 执行完毕 后,立即执行当前微任务队列中的所有微任务(依次执行)

    • 执行完第一个宏任务中的同步任务后, 执行微任务队列中的微任务;

      三、执行微任务
      // 微任务1
      new Promise((resolve, reject) => {
        resolve(8)
      }).then(res => {
        console.log(res)
      })
      
      // 微任务2
      new Promise((resolve, reject) => {
        resolve(5)
      }).then(res => {
        console.log(res)
      })
      
      // 控制台打印 8 5
      
  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染

    • 进行DOM 渲染

      document.querySelector('div').style.color = 'blue' 
      
  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)

    • 开始执行第二个宏任务

      // 第二个宏任务
      setTimeout(() => {
        // 同步任务4
        console.log(3)
        // 微任务3
        new Promise((resolve, reject) => {
          resolve(7)
        }).then(res => {
          console.log(res)
        })
        // 同步任务5
        console.log(4)
      }, 0)
      
    • 先执行同步任务(同步任务4、同步任务5), 将微任务添加至微任务队列

      // 同步任务4
      console.log(3)
      
      // 同步任务5
      console.log(4)
      
      // 控制台打印 3 4
      
    • 同步任务执行完成后, 执行微任务队列中的为任务

      // 微任务3
      new Promise((resolve, reject) => {
        resolve(7)
      }).then(res => {
        console.log(res)
      })
      
      // 控制台打印 7
      
    • DOM渲染

    • 进入下一个宏任务

    • 如没有宏任务, 则进入休眠状态…


  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值