事件循环理解-微任务和宏任务的执行顺序

272 阅读3分钟

宏任务

是指消息队列中的等待被主线程执行的事件,宏任务执行时都会重新创建栈,然后调用宏任务中的函数,栈也会随着变化,但宏任务执行结束时,栈也会随之销毁。

  • 宏任务一般为浏览器或者Node发起的,包括整体代码script,setTimeout,setInterval ,setImmediate,I/O,UI renderingnew ,Promise*

微任务

可以把微任务看成是一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前

  • 微任务一般为js引擎发起的,包括 Promises.(then catch finally),process.nextTick, MutationObserver,微任务是基于消息队列、事件循环、UI 主线程还有堆栈而来的

自己理解的事情循环(Event Loop)

  • 主线程从上往下执行同步代码,形成一个执行栈。
  • 除执行栈外,还有任务队列,任务队列分为微任务队列,宏任务队列,只要异步任务有了运行结果,就把对应的事件放入任务队列中,微任务放到微任务队列,宏任务放到宏任务队列中。
  • 一旦执行栈中的代码执行完毕,执行栈为空时,就会读取任务队列中的代码到执行栈中执行,先读取微任务队列,等当前全部微任务执行完毕后,(如果执行环境是浏览器的话,可能会渲染页面(这要看是否有操作DOM,没有就直接跳过)),再读取宏任务队列执行。
  • 主线程不断重复以上操作。

为什么微任务先执行

因为当我们的主线程的代码执行完毕之后,在Event Loop执行之前,首先会尝试DOM渲染,这个时候,微任务是在DOM渲染之前执行,DOM渲染完成了之后,会执行宏任务,

练习题

setTimeout(()=>{console.log(1)},0)
setTimeout(()=>{
    new Promise((res,rej)=>{
    res(2)
}).then(res=>{
    console.log(res)
    setTimeout(()=>{console.log(3)},0)
    new Promise((res,rej)=>{
    res(4)
}).then(res=>{
        console.log(res)
})
})
},0)
setTimeout(()=>{console.log(5)},0)
//输出 1,2,4,5,3

理解

  • 先最外层的三个延时器开启,其回调是宏任务,所以按照得到结果的顺序执行(这里就是倒计时结束),所以是从上到下执行。先输出1。
  • 然后执行第二个延时器回调,里面实例Promise,实例Promise是同步的,then方法是微任务,然后先输出2,再开启一个定时器,把这宏任务推到宏任务队列最后,又一个then,需要先将微任务执行完,再执行宏任务,所以输出4
  • 最后输出5,3

补充

执行栈清空之后,首先执行当前的微任务,再去尝试DOM渲染,最后触发EventLoop机制,执行宏任务。

//一个没有内容的节点
let box = document.querySelector('.box')

let span = document.createElement('span')
span.innerText = '加油'
box.appendChild(span);
console.log(box.children.length)

//微任务-DOM渲染之前执行
let span2 = document.createElement('span')
span2.innerText = '加油'
box.appendChild(span2);
 Promise.resolve().then(() => {
        const length = box.children.length;
        alert(`微任务 ${length}`)
    })
    
//宏任务-DOM渲染之后执行
let span3 = document.createElement('span')
span3.innerText = '加油'
box.appendChild(span3);
setTimeout(()=>{
    const length = box.children.length;
        alert(`微任务 ${length}`)
},0)

图解

image.png

注-部分引用: