前言
·什么是宏观任务,什么是微观任务?
宏任务:宿主(浏览器)发起的任务我们可以称之为宏观任务(macrotask) script、setTimeout、setInterval、I/O、UI交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)
微任务:引擎(js)自己发起的任务叫做微观任务(microtask) promise async await
·js执行宏任务和微任务的联系:
先执行宏任务再执行当前宏任务内的微任务(在此过程中将遇到的微宏任务依次推入到eventqueue, 先进入eventqueue的任务会被先执行),执行完当前宏任务内的所有微任务之后,再执行下一个宏任务, 以此循环,当然在微宏任务中同步任务会优先执行,而整体的scrpt标签就是第一个执行的宏任务
·Promoise相关
Promise中的异步体现在then和catch中,所以写在Promise中的代码是被当做同步任务立即执行的。 而在async/await中,在出现await出现之前,其中的代码也是立即执行的实际上await是一个让出线程的标志。 await后面的表达式会先执行一遍,将await后面的代码加入到microtask(微观任务)中
·示例(最近看到的经典题)
题解:
1.script是第一个宏任务
2.先执行当前宏观里的所有同步任务(主线程任务),再执行当前宏观里的所有微观任务,再执行下一个宏观任务,
即script宏(同->微)->下一个宏(同->微)->....
同步任务队列 console.log(4)-> async1()-> async2()-> console.log(8)
console.log(4); 同步任务 所以第一步打印4
async1(); 返回的是一个promise,promise本身是同步,所以 第二步打印1,并且将await之后的放到微观任务队列
async2(); 同步任务打印3,
promise();promise.then之前同步任务打印6,并将promise.then异步任务放到微观任务队列
console.log(8);同步任务打印8
微观任务队列 consol.log(2)-> console.log(7)
consol.log(2) 微观任务打印2
console.log(7) 微观任务打印7
宏观任务 setTimeout
setTimeout(function(){
console.log(5) 宏观任务打印5
})
打印顺序
同步4->同步1->同步3->同步6->同步8->微观2->微观7->宏观->5
·其它案例(来小试牛刀一下吧)
题一:
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
new Promise(resolve => {
console.log('promise1')
resolve()
})
.then(() => {
console.log('promise2')
})
}
console.log('script start')
setTimeout(() => {
console.log('setTimeout')
}, 0);
async1()
new Promise(resolve => {
console.log('promise3')
resolve()
})
.then(() => {
console.log('promise4')
})
console.log('script end')
题二:
Promise.resolve().then(()=>{
console.log('Promise1')
setTimeout(()=>{
console.log('setTimeout2')
},0)
})
setTimeout(()=>{
console.log('setTimeout1')
Promise.resolve().then(()=>{
console.log('Promise2')
})
},0)
题三:
async function async1() {
console.log('async1 start')
await async2()
setTimeout(() => {
console.log('setTimeout1')
}, 0)
}
async function async2() {
setTimeout(() => {
console.log('setTimeout2')
}, 0)
}
console.log('script start')
setTimeout(() => {
console.log('setTimeout3')
}, 0);
async1()
new Promise(resolve => {
console.log('promise1')
resolve()
})
.then(() => {
console.log('promise2')
})
1."script start"
"async1 start"
"promise1"
"promise3"
"script end"
"promise2"
"async1 end"
"promise4"
"setTimeout"
2.
"Promise1"
"setTimeout1"
"Promise2"
"setTimeout2"
3.
"script start"
"async1 start"
"promise1"
"script end"
"promise2"
"setTimeout3"
"setTimeout2"
"setTimeout1"
链接:https://juejin.cn/post/7057453597415440414
事件循环
JavaScript 语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。为了协调事件、用户交互、脚本、UI 渲染和网络处理等行为,防止主线程的不阻塞,Event Loop 的方案应用而生。Event Loop 包含两类:一类是基于 Browsing Context,一种是基于 Worker。二者的运行是独立的,也就是说,每一个 JavaScript 运行的"线程环境"都有一个独立的 Event Loop,每一个 Web Worker 也有一个独立的 Event Loop。
本文所涉及到的事件循环是基于 Browsing Context。
任务队列
根据规范,事件循环是通过任务队列的机制来进行协调的。一个 Event Loop 中,可以有一个或者多个任务队列(task queue),一个任务队列便是一系列有序任务(task)的集合;每个任务都有一个任务源(task source),源自同一个任务源的 task 必须放到同一个任务队列,从不同源来的则被添加到不同队列。setTimeout/Promise 等API便是任务源,而进入任务队列的是他们指定的具体执行任务。
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:
- 在此次 tick 中选择最先进入队列的任务(oldest task),如果有则执行(一次)
- 检查是否存在 Microtasks,如果存在则不停地执行,直至清空 Microtasks Queue
- 更新 render
- 主线程重复执行上述步骤
在上诉tick的基础上需要了解几点:
- JS分为同步任务和异步任务
- 同步任务都在主线程上执行,形成一个执行栈
- 主线程之外,事件触发线程管理着一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
- 一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。
宏任务
(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 环境)
微任务
microtask,可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task之前,在渲染之前。
所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。
微任务包含:
Promise.then
Object.observe
MutationObserver
process.nextTick(Node.js 环境)
运行机制
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:
- 执行一个宏任务(栈中没有就从事件队列中获取)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
- 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
- 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
如图:
例子
{ // script宏
Promise.resolve().then(()=>{ // 微 异步
console.log('第一个回调函数:微任务1')
setTimeout(()=>{ // 宏
console.log('第三个回调函数:宏任务2')
},0)
})
setTimeout(()=>{ // 宏
console.log('第二个回调函数:宏任务3')
Promise.resolve().then(()=>{ // 微
console.log('第四个回调函数:微任务4')
})
},0)
// script > [<microtask微>Promise.then(微任务1)],[<macrotask宏>setTimeout(宏任务3)]
// > Promise.then(微任务1) > [<macrotask宏>setTimeout(宏任务3),setTimeout(宏任务2)]
// > setTimeout(宏任务3) > [<microtask微>Promise.then(微任务4)],[<macrotask宏>setTimeout(宏任务2)]
// > Promise.then(微任务4) > [<macrotask宏>setTimeout(宏任务2)]
// > setTimeout(宏任务2)
// 第一个回调函数:微任务1
// 第二个回调函数:宏任务3
// 第四个回调函数:微任务4
// 第三个回调函数:宏任务2
}