宏任务
宏任务在主线程上执行,包括:
- 渲染时间(解析DOM、计算布局、放大缩小等)
- 用户交互事件(如鼠标点击、滚动页面、放大缩小等)
- JavaScript脚本执行时间
- 网络请求完成、文件读写完成事件
主线程采用for循环,从任务队列中取出任务并执行任务,队列里的任务就叫宏任务。宏任务是主线程添加到任务队列中的,不能控制它在任务队列的位置也就不能控制任务开始的时间,因此也会产生一些问题。
function timerCallback2(){
console.log(2)
}
function timerCallback(){
console.log(1)
setTimeout(timerCallback2,0)
}
setTimeout(timerCallback,0)
上面代码设置了两个setTimeout的回调任务,计划是它们两个任务先后次序执行,中间没有其他任务,现实情况是不可控的,两个回调任务中间有可能会插入很多其他任务,影响第二个回调任务的执行。
微任务
当前宏任务执行时,有时候会产生多个微任务,这时候就需要用微任务队列来保存这些微任务了。也就是说每个宏任务都关联了一个微任务队列,接下来分析两个最要的问题,一个是微任务是什么时间产生的,还有一个微任务是什么时间执行的? 首先微任务有两种方式产生:
- 第一种方式是使用MutationObserver 监控DOM节点,DOM发生了变化,observer会把变化记录在变化数组中,等待一起都结束了,然后一次性的从变化数组中执行对应的回调函数。当DOM节点变化时,就会产生DOM变化记录的微任务。
- 第二种方式是使用Promise,当调用Promise.resolve()或者Promise.reject()的时候,也会产生微任务。
产生微任务之后,JavaScript引擎会把他加入到微任务队列中。
JavaScript脚本执行的过程中,分别通过Promise和removeChild创建了两个微任务,并添加到微任务列表中。JavaScript脚本执行结束之后,准备退出全局执行上下文时,到达检查点,JavaScript引擎会检查微任务队列,发现微任务队列中有微任务,接下来会依次执行这两个微任务。等微任务队列清空之后退出全局执行上下文。
一道面试题引发的思考
console.log(1)
setTimeout(function(){
console.log(2)
new Promise(function(resolve){
resolve(3)
}).then(function(res){
console.log(res)
})
}, 100)
new Promise(function(resolve){
console.log(5)
resolve()
}).then(function(){
console.log(6)
})
setTimeout(function(){
console.log(7)
new Promise(function(resolve){
console.log(8)
resolve()
}).then(function(){
setTimeout(() => {
console.log(9)
}, 100)
})
})
console.log(10)
首先,当代码执行的时候,整体代码script进入宏任务队列中,并开始执行宏任务。首先执行console.log(1),执行完后移出调用栈。
接下来将setTimeout(callback,100)推入调用栈,setTimeout()方法属于事件循环模型中WebAPIs中的方法,引擎在将setTimeout()方法出栈执行时,将延时执行的函数交给了相应模块,即图右方的timer模块来处理。
接着将Promise语句推入调用栈,执行console.log,接下来是promise.then方法,这是一个微任务,将其推入微任务队列。这是new Promise执行完毕,移出调用栈。
接着执行console.log(10),这时script宏任务执行结束,移出宏任务队列。接下来清空微任务队列。首先执行的是Promise then,推入调用栈中。执行console.log(6),then方法执行完成移出调用栈和微任务队列。
接下来又是setTimeout方法,同样是将setTimeout方法交给了相应模块。
timer模块检测到setTimeout2达到触发条件,将setTimeout2 callback推入宏任务队列,此时调用栈为空,引擎轮询检测任务队列是否有任务执行,将setTimeout2 callback推入调用栈,执行console.log(7)。
接着是将new Promise推入执行栈,执行console.log(8),接下来是promise.then方法,这是一个微任务,将其推入微任务队列。这是new Promise执行完毕,移出调用栈。
这时候宏任务setTimeout2 callback执行结束,移出执行栈,紧接着开始清空微任务队列。接着执行Promise then方法,接着将setTimeout3函数交给timer模块来处理。then方法执行完成,调用栈和微任务队列都被清空。
timer模块检测到setTimeout1达到触发条件,将setTimeout1 callback推入宏任务队列,此时调用栈为空,引擎轮询检测任务队列是否有任务执行,将setTimeout1 callback推入调用栈,执行console.log(2)。
接着是将new Promise推入执行栈,然后是promise.then方法,这是一个微任务,将其推入微任务队列。这是new Promise执行完毕,移出调用栈。setTimeout1也执行完成,移出调用栈。
宏任务执行结束,紧接着清空微任务队列,执行Promise then方法,执行console.log(res)。
timer模块检测到setTimeout3达到触发条件,将setTimeout3 callback推入宏任务队列,此时调用栈为空,引擎轮询检测任务队列是否有任务执行,将setTimeout3 callback推入调用栈,执行console.log(9)。