前言:JavaScript是一个单线程的脚本语言。就是说在一行代码执行的过程中,必然不会存在同时执行的另一行代码。同一时间只能做一件事,为了协调事件、用户交互、脚本、UI渲染和网络处理行为,防止主线程的不阻塞,Event Loop的方案应用而生。
一、宏任务和微任务的区别
(1)宏任务
(macro)task,可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事事件队列中获取一个事件回调并放到执行栈中执行)。
游览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个(macro)task执行结束后,在下一个(macro)task执行开始前,对页面进行重新渲染,流程如下:
(macro)task ->渲染->(macro)task->...
宏任务包含:
script(整体代码)
setTimeout
setInterval
I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js环境)
(2)微任务
microtask,可以理解是在当前task执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task之前,在渲染之前。
所以,它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。
微任务包含:
Promise.then
Object.observe
MutaionObserver
process.nextTick(Node.js环境)
(3)运行机制
在事件循环中,没进行一次循环操作称为tick,每一次tick的任务处理模型是比较复杂的,但关键步骤如下:
- 执行一个宏任务(栈中没有就从事件队列中获取);
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中;
- 宏任务执行完成后,立即执行当前微任务队列中的所有微任务(依次执行);
- 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染;
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)。
如图:
(4)宏任务与微任务的区别
任务队列中的都是已经完成的异步操作,而不是说注册一个异步任务就会被放在这个任务队列中。而且一个宏任务在执行的过程中,是可以 添加一些微任务的。在当前的微任务没有执行完成时,是不会执行下一个宏任务的。
二、经常在面试题、各种博客中出现的代码片段
setTimeout(_ =>console.log(4))
new Promise(resolve => {
resolve()
console.log(1)
}).then(_ => {
console.log(3)
})
console.log(2)
setTimeout就是作为宏任务来存在的,而Promise.then则是具有代表性的微任务,上述代码的执行顺序就是按照序号来输出的。
所有会进入的异步都是指的事件回调中的那部分代码
也就是说,new Promise在实例化的过程中所执行的代码都是同步进行的,而then中注册的回调才是异步执行的。
在同步代码执行完成后才回去检查是否有异步任务完成,并执行对应的回调,而微任务又会在宏任务之前执行。
所以,就得到了上述的输出结论1、2、3、4。
+部分表示同步执行的代码
+setTimeout(_ => {
- console.log(4)
+})
+new Promise(resolve => {
+ resolve()
+ console.log(1)
+}).then(_ => {
- console.log(3)
+})
+console.log(2)
本来setTimeout已经先设置了定时器(相当于取号),然后在当前进程中又添加了一些Promise的处理(临时添加业务)。
参考博客:微任务、宏任务与Event-Loop
js中的宏任务与微任务