我们先来看一道关于宏任务和微任务的一道面试题:
console.log('start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
new Promise((resolve) => {
console.log('promise')
resolve()
})
.then(() => {
console.log('then1')
})
.then(() => {
console.log('then2')
})
console.log('end')
试问一下上面代码的执行顺序是啥?难道js不是一行一行执行的?
我有点慌,于是我粘贴到浏览器去瞅两眼:
结果为:
start
promise
end
then1
then2
setTimeout
这个就涉及到JavaScript事件轮询中的宏任务和微任务。那么,首先,我们需要先知道JS运行机制。
关于JavaScript
JavaScript是一门单线程语言,即一次只能完成一个任务,若有多个任务要执行,则必须排队按照队列来执行(前一个任务完成,再执行下一个任务)。
- JS是单线程执行:指的是JS 引擎线程。
- 宿主环境:JS运行的环境。一般为浏览器或者Node。
- 执行栈:是一个存储函数调用的栈结构,遵循先进后出的原则。
JavaScript事件循环
- 既然js是单线程,那就像只有一个收银台的大超市,人们需要排队一个一个结账,同理js任务也要一个一个顺序执行。这种模式执行简单,但随着日后的需求,事务,请求增多,这种单线程模式执行效率必定低下。只要有一个任务执行消耗了很长时间,在这个时间里后面的任务无法执行。
- 常见的有新闻类网站包含的超清图片加载很慢,难道我们的网页要一直卡着直到图片完全显示出来?为了解决这个问题,JavaScript语言将任务执行模式分成同步和异步:
- 同步模式: 就是上面所说的一种执行模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的。
- 异步模式: 就是每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。
宏任务和微任务
ES6 规范中,microtask 称为 jobs,macrotask 称为 task
宏任务是由宿主发起的,而微任务由JavaScript自身发起。
宏任务(macrotask)包括
- script整体代码
- setTimeout
- setInterval
- setImmediate
- Ajax
- DOM事件
微任务(microtask)包括
- process.nextTick
- MutationObserve
- Promise.then catch finally
微任务比宏任务的执行时间要早
宏任务和微任务执行的顺序
- 因为微任务不需要执行上下文(这里指的是资源的切换),所以它可以在一次上下文切换间隔中把所有的微任务都做掉。
- 而宏任务因为需要切换上下文,所以他会慢点执行。
- 所以会先执行已注册的微任务,然后是宏任务、
- 这样,就相当于做了一次上下文的切换,如果在此期间,又有新的微任务或宏任务被注册了,就不断重复上面的这个流程。