宏任务
是指消息队列中的等待被主线程执行的事件,宏任务执行时都会重新创建栈,然后调用宏任务中的函数,栈也会随着变化,但宏任务执行结束时,栈也会随之销毁。
- 宏任务一般为浏览器或者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)
图解
注-部分引用: