JavaScript 宏任务、微任务

JavaScript 宏任务、微任务

    • JS 运行机制
    • 浏览器事件循环机制
      • 扩展小知识 Vue 中的 `$nextTick`
    • 宏任务 微任务
      • 基础概念
      • 微任务的产生
      • 示例
      • 浅谈 async await
      • 扩展小知识 setTimeout
    • Node 事件循环
    • 推荐文章

在了解宏任务与微任务之前,我们需要知道基础的几个 JS 运行机制的概念

JS 运行机制

  1. JS 是单线程执行

描述: 执行 JS 代码的线程只有一个 是浏览器提供的 JS 引擎线程 (主线程)

function person() {
	play()
}
function play() {
	swim()
}
function swim() {
	throw new Error('error')
}
person()

JavaScript 宏任务、微任务_第1张图片
有一个人打算去玩,然后去游泳,遇到危险
报错的时候,遵循 先进后出的原则,这不就是栈的特性嘛——这也就是执行栈
2. 那么 JS 是如何异步执行的呢?
JavaScript 宏任务、微任务_第2张图片
【答案】: 浏览器是多线程的,当 JS 需要执行异步任务的时候,浏览器帮助我们另外启动一个线程
【举例】浏览器包含 HTTP 请求线程,当主线程请求数据的时候,将任务给另一个浏览器线程,也就是说浏览器才是真正执行发送请求任务的角色,JS 只是执行最后的回调处理
【总结】浏览器渲染进程提供 的多个线程为 JS的异步执行提供了基础
3. 浏览器异步任务的执行原理是事件驱动
JavaScript 宏任务、微任务_第3张图片

  • 事件可以是人为触发的也可以是程序自动触发(浏览器定时器线程)
  • 事件驱动与 状态驱动或数据驱动的区别
    • 事件驱动举例: 如图,点击方向,通过事件驱动人像移动
    • 状态驱动活或数据驱动: 点击方向,修改人像的位置,通过定时器判断人像是否发生变化,然后进行人像移动,即根据数据的而变化来判断是否需要重新渲染
    • 浏览器的点击事件会暂时进入到队列中,等待 JS 的同步任务执行完成后,就会从队列中取出要执行的队列,要执行的顺序由事件循环机制决定
      JavaScript 宏任务、微任务_第4张图片

浏览器事件循环机制

JavaScript 宏任务、微任务_第5张图片
Event Loop中,每一次循环称为tick,每一次tick的任务如下:

  • 执行栈选择最先进入队列的宏任务(一般都是script),执行其同步代码直至结束;
  • 检查是否存在微任务,有则会执行至微任务队列为空;
  • 如果宿主为浏览器,可能会渲染页面;
  • 开始下一轮tick,执行宏任务中的异步代码(setTimeout等回调)。

扩展小知识 Vue 中的 $nextTick

vm.$nextTick 接受一个回调函数作为参数,用于将回调延迟到下次DOM更新周期之后执行。

这个API就是基于事件循环实现的。
“下次DOM更新周期”的意思就是下次微任务执行时更新DOM,而vm.$nextTick就是将回调函数添加到微任务中(在特殊情况下会降级为宏任务)。
若想知道更多Node.js事件循环,请查看:Node.js 事件循环,定时器和 process.nextTick()

什么是宏任务微任务

宏任务 微任务

基础概念

  1. ES6 规范中,microtask 称为 jobs,macrotask 称为 task
  2. 宏任务是由宿主发起的,而微任务由JavaScript自身发起。
  3. 宏 (Macro)是一种批量处理的称谓
宏任务(Macro) 微任务(micro)
谁发起的 宿主 JavaScript
具体事件 script、setTimeout、setInterval、I/O等 .then、 catch、 finally process.nextTick(Node.js)new MutaionObserver()等…… (new Promise 不是一个微任务)
会触发新一轮Tick吗 不会
数量 可能有多个 只有一个
特征 有明确的异步任务需要执行和回调,需要其他异步线程支持 没有明确异步任务执行,只有回调,不需要其他异步线程支持

执行程序:

  • 每个宏任务,都单独关联了一个微任务队列
  • 执行顺序为: 主线程> 微任务>宏任务 【通俗的记忆方法:先从小事做起】
  • 区分宏任务与微任务:
    • 【举例】
      • 定时器等待任务,需要定时器线程执行 =>(宏任务)
      • Ajax 发送请求任务,需要 HTTP 线程执行 =>(宏任务)
      • promise.then 没有任何异步任务需要其他线程执行,只有回调 =>(微任务)

微任务的产生

  • 在ES3以及以前的版本中,JavaScript本身没有发起异步请求的能力,也就没有微任务的存在。

  • 在ES5之后,JavaScript引入了Promise,这样,不需要浏览器,JavaScript引擎自身也能够发起异步任务了。

【问题】宏任务、微任务都可以操作异步,那微任务要解决的问题?
【答案】为了解决主线程任务过多的时候,异步回调等待时间过长的问题,可以在实时性和效率之间做一个有效的权衡

示例

	  console.log(11111)

      setTimeout(() => {
        console.log(22222)
      })

      new Promise(resolve => {
        resolve()
        console.log(3333)
      }).then(() => {
        console.log(44444)
      }).finally(() => {
        console.log(55555)
      })

      console.log(66666)

输出为: 136452
解析:

  1. 顺序执行,输出1
  2. 执行 setTimeout 宏应用,进入宏任务队列
  3. 执行 Promise 输出 3
  4. .then .finally 微应用进入微应用队列(这里就和事件的循环机制有关)
  5. 顺序执行 ,输出 6
  6. 微应用执行比宏应用执行更快,因此输出 451

说起 Promise ,不禁想起 Vue 中的 async await 将异步转为同步

浅谈 async await

console.log('script start')

async function async1() {
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2 end')
}
async1()

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

new Promise(resolve => {
  console.log('Promise')
  resolve()
})
  .then(function() {
    console.log('promise1')
  })
  .then(function() {
    console.log('promise2')
  })

console.log('script end')

你预想的答案是什么呢?正确答案如下:

script start
 async2 end
Promise
script end
async1 end
 promise1
promise2
setTimeout

最让你意想不到的位置,应该是 async1 end 的位置了,那为什么他又在这个位置呢?

async function async1() {
  await async2()
  console.log('async1 end')
}
async function async2() {
  console.log('async2 end')
}
async1()

转为ES5的写法:

new Promise((resolve, reject) => {
  // console.log('async2 end')
  async2() 
  ...
}).then(() => {
 // 执行async1()函数await之后的语句
  console.log('async1 end')
})

不禁看到 输出在 微任务 .then 里面

扩展小知识 setTimeout

读上述的文章可知,浏览器执行顺序

  • 同步任务,
  • 再去取出异步回调执行
  • 执行 setTimeout时,浏览器启动新的线程去计时,
  • 计时结束后触发定时器事件,将回调存入宏任务队列

如果同步任务和微任务执行的时间过长,此时的宏任务就只能被挂起了,这就造成了计时器不准确的问题。
【现象】使用setInterval 设置时间的时候,会发现跳秒现象
【进一步解释】微任务执行完毕之后,也就是队列执行完毕后,浏览器会执行视图渲染,这里会有浏览器的优化,他可能会合并多次事件循环的结果
【得出结果】视图更新,会先执行 requestAnimationFrame 回调,不是在每一次操作 Dom 之后,而是队列执行完毕后,执行一次视图

Node 事件循环

JS 不实现事件循环,都是由他的宿主实现的,Node 与浏览器的实现大致相同。如有不同,请查看JS事件循环

推荐文章

宏任务和微任务到底是什么?
Node.js 事件循环,定时器和 process.nextTick()
JS事件循环
requestAnimationFrame

你可能感兴趣的:(JavaScript,javascript,node.js,vue.js)