目錄
前言
一、 宏任務(wù)、微任務(wù)的基本概念
1.宏任務(wù)介紹
2.微任務(wù)介紹
3.宏任務(wù)、微任務(wù)發(fā)展及出現(xiàn)的原因:
?4.宏任務(wù)、微任務(wù)的基本類型
二、?事件循環(huán)模型(Event Loop)
三、 Promise、async和await 在事件循環(huán)中的處理
1.Promise:
????????2. async/await :
這個(gè)知識(shí)點(diǎn)比較容易忽略,以為 await 回來后,直接繼續(xù)執(zhí)行后面的代碼,這是不對(duì)的!
四、 process.nextTick在事件循環(huán)中的處理
五、 典型案例
5.1 典型的宏任務(wù)、微任務(wù)
5.2 宏任務(wù)中包含微任務(wù)
5.3 微任務(wù)中包含宏任務(wù)
5.4 async 宏任務(wù)
5.5 node 事件執(zhí)行分析
5.6 綜合案例
六、案例分析
6.1 典型的宏任務(wù)、微任務(wù)
6.2 宏任務(wù)中包含微任務(wù)
6.3 微任務(wù)中包含宏任務(wù)
6.4 async 宏任務(wù)
6.5 node 事件執(zhí)行分析
6.6 綜合案例
總結(jié)起來,宏任務(wù)與微任務(wù)的執(zhí)行順序可以歸納為以下幾點(diǎn):
綜上所述,宏任務(wù)與微任務(wù)的執(zhí)行順序是宏任務(wù)隊(duì)列執(zhí)行完畢之后依次執(zhí)行微任務(wù)隊(duì)列,然后再從宏任務(wù)隊(duì)列中取出下一個(gè)宏任務(wù)進(jìn)行執(zhí)行。在實(shí)際開發(fā)中,我們可以根據(jù)這個(gè)執(zhí)行順序來合理地安排任務(wù)的執(zhí)行,以達(dá)到更好的效果。
前言
寫此之前,也查閱了很多文章,并結(jié)合自己的理解,說說對(duì)Event Loop模型的理解、以及對(duì)Promise、async/await在任務(wù)隊(duì)列中的影響進(jìn)行了分析,也給出了多種情形的任務(wù)案例以及分析解釋,相信大家看完會(huì)有所收獲;當(dāng)然,也是自己的理解,難免有所偏差,歡迎大家指正~
一、 宏任務(wù)、微任務(wù)的基本概念
1.宏任務(wù)介紹
宏任務(wù)(Macro Task)是指由主線程上的事件觸發(fā)器(Event Loop)進(jìn)行調(diào)度的任務(wù)。宏任務(wù)包括但不限于如下幾種情況:主線程上的代碼塊、setTimeout、setInterval、I/O 操作、DOM 事件等。
2.微任務(wù)介紹
微任務(wù)(Micro Task)是指由其他任務(wù)觸發(fā)的任務(wù)。它們的優(yōu)先級(jí)比宏任務(wù)更高,會(huì)在宏任務(wù)隊(duì)列為空時(shí)立即執(zhí)行。微任務(wù)包括但不限于如下幾種情況:Promise 的回調(diào)函數(shù)、MutationObserver 的回調(diào)函數(shù)等。
3.宏任務(wù)、微任務(wù)發(fā)展及出現(xiàn)的原因:
JavaScript是典型的單線程(自上而下依次執(zhí)行代碼),但是,一個(gè)任務(wù)耗時(shí)過長(zhǎng),或多個(gè)任務(wù)需要執(zhí)行時(shí),勢(shì)必導(dǎo)致線程阻塞,影響視圖渲染效果。
在ES3以及以前的版本中,JavaScript本身沒有異步任務(wù)能力,隨著Promise的引入,JavaScript引擎自身也能夠發(fā)起異步任務(wù)了。至此,JavaScript就可分為同步任務(wù)及異步任務(wù);而JS又把異步任務(wù)做了進(jìn)一步的劃分,分為宏任務(wù)與微任務(wù)。
由于微任務(wù)執(zhí)行快,一次性可以執(zhí)行很多個(gè),在當(dāng)前宏任務(wù)執(zhí)行后立刻清空微任務(wù)可以達(dá)到偽同步的效果,這對(duì)視圖渲染效果起到至關(guān)重要的作用,這也是區(qū)分宏任務(wù)、微任務(wù)的原因。
?4.宏任務(wù)、微任務(wù)的基本類型
宏任務(wù):
-
script(外層同步代碼)
-
Ajax請(qǐng)求
-
setTimeout、setInterval
-
postMessage、MessageChannel
-
setImmediate(Node.js 環(huán)境)、I/O(Node.js 環(huán)境)
-
...
微任務(wù)
-
Promise.then().catch() 和 .finally()
-
process.nextTick(Node.js 環(huán)境)
-
...
二、?事件循環(huán)模型(Event Loop)
如上圖,當(dāng)同步代碼執(zhí)行完畢后,就會(huì)執(zhí)行所有的宏任務(wù),宏任務(wù)執(zhí)行完成后,會(huì)判斷是否有可執(zhí)行的微任務(wù);如果有,則執(zhí)行微任務(wù),完成后,執(zhí)行宏任務(wù);如果沒有,則執(zhí)行新的宏任務(wù),形成事件循環(huán)。
這只是圖示宏任務(wù)及微任務(wù)的執(zhí)行關(guān)系,那么,js在 Event Loop中究竟是如何調(diào)用方法去處理的呢?
- 我們寫的代碼,js會(huì)進(jìn)行任務(wù)類型分配,根據(jù)類型放入不同的任務(wù)隊(duì)列中;
- Event Loop 會(huì)根據(jù)執(zhí)行時(shí)機(jī),將需要執(zhí)行的函數(shù),壓入事件調(diào)用堆棧中執(zhí)行;
- 相同的宏任務(wù),也會(huì)有不同的執(zhí)行時(shí)機(jī),(類似 setTimeout 的 time),Event Loop 會(huì)控制將需要執(zhí)行的函數(shù)壓入
總結(jié):Event Loop 在壓入事件時(shí),都會(huì)判斷微任務(wù)隊(duì)列是否還有需要執(zhí)行的事件:如果有,則優(yōu)先將需要執(zhí)行的微任務(wù)壓入;沒有,則依次壓入需要執(zhí)行宏任務(wù)!
切記,宏任務(wù)執(zhí)行完畢后,都會(huì)判斷是否還有需要執(zhí)行的微任務(wù)?。。≡趶?fù)雜的事件中,該點(diǎn)經(jīng)常會(huì)錯(cuò)?。?!
切記,宏任務(wù)執(zhí)行完畢后,都會(huì)判斷是否還有需要執(zhí)行的微任務(wù)!??!在復(fù)雜的事件中,該點(diǎn)經(jīng)常會(huì)錯(cuò)?。?!
切記,宏任務(wù)執(zhí)行完畢后,都會(huì)判斷是否還有需要執(zhí)行的微任務(wù)?。。≡趶?fù)雜的事件中,該點(diǎn)經(jīng)常會(huì)錯(cuò)?。?!
三、 Promise、async和await 在事件循環(huán)中的處理
1.Promise:
new Promise 創(chuàng)建實(shí)例的過程是同步的哦!
console.log(1);
new Promise((resolve, reject) => {
console.log(2);
})
console.log(3);
// 結(jié)果: 1 2 3
但是,Promise.then().catch().finally()中的回調(diào),是微任務(wù)。
console.log(1);
new Promise((resolve, reject) => {
console.log(2);
resolve(); // 觸發(fā) then 回調(diào)
// reject(); // 觸發(fā) catch 回調(diào)
}).then(()=>{
console.log('then')
}).catch(()=>{
console.log('catch')
}).finally(()=>{
console.log('finally')
})
console.log(3);
// 結(jié)果: 1 2 3 then finally
上圖是菜鳥教程-JavaScript Promise 中對(duì)Promise的講解,我們想一下為啥要這樣設(shè)計(jì):
我們假設(shè)Promise 不是立即執(zhí)行的,會(huì)有什么后果?利用Promise,多是封裝異步請(qǐng)求(Ajax),而請(qǐng)求不是立即請(qǐng)求的,還需要等待Promise 任務(wù)執(zhí)行,那么我們就失去了網(wǎng)絡(luò)請(qǐng)求的時(shí)效性,會(huì)導(dǎo)致頁面等待渲染(因?yàn)槲覀兩厦嫣峒暗?Event Loop 會(huì)根據(jù)事件執(zhí)行時(shí)機(jī),選擇將事件壓入堆棧中,我們無法保證自己寫的【不是立即執(zhí)行Promise】什么時(shí)候執(zhí)行)。
????????2. async/await :
簡(jiǎn)單來說,async是通過Promise包裝異步任務(wù)。
async function fun1() {
console.log('fun1 start')
await fun2(); // 等待 fun2 函數(shù)執(zhí)行完成
console.log('fun1 end')
}
async function fun2() {
console.log('fun2 start')
console.log('fun2 end')
}
fun1()
// 輸出結(jié)果: fun1 start、fun2 start、fun2 end、fun1 end
遇到 await 則需要等待 await 后的代碼執(zhí)行完成后,在往下執(zhí)行代碼。因此,可以將 await 看作搶奪線程的標(biāo)記,fun1 中,本來是同步執(zhí)行的,但是 await 的出現(xiàn),導(dǎo)致線程執(zhí)行了 fun2 的代碼后,再次回到await,往后執(zhí)行。
同時(shí),await后面的代碼,會(huì)進(jìn)入then微任務(wù)中!!!
同時(shí),await后面的代碼,會(huì)進(jìn)入then微任務(wù)中!!!
同時(shí),await后面的代碼,會(huì)進(jìn)入then微任務(wù)中!!!
async function fun1() {
console.log('fun1 start')
await fun2(); // 等待 fun2 函數(shù)執(zhí)行完成
console.log('我是 await 后面的代碼')
console.log('fun1 end')
}
async function fun2() {
console.log('fun2 start')
console.log('fun2 end')
}
fun1()
console.log('await 阻塞,導(dǎo)致 await后面代碼進(jìn)入 then 微任務(wù)')
這個(gè)知識(shí)點(diǎn)比較容易忽略,以為 await 回來后,直接繼續(xù)執(zhí)行后面的代碼,這是不對(duì)的!
四、 process.nextTick在事件循環(huán)中的處理
process.nextTick是Node環(huán)境的變量,process.nextTick() 是一個(gè)特殊的異步API,其不屬于任何的Event Loop階段。事實(shí)上Node在遇到這個(gè)API時(shí),Event Loop根本就不會(huì)繼續(xù)進(jìn)行,會(huì)馬上停下來執(zhí)行process.nextTick(),這個(gè)執(zhí)行完后才會(huì)繼續(xù)Event Loop。所以,nextTick和Promise同時(shí)出現(xiàn)時(shí),肯定是process.nextTick() 先執(zhí)行。
可以類比 Vue.$nextTick(),也是需要執(zhí)行完這個(gè)函數(shù)后,才能繼續(xù)Event Loop。
五、 典型案例
5.1 典型的宏任務(wù)、微任務(wù)
setTimeout(function () {
console.log('1');
});
new Promise(function (resolve) {
console.log('2');
resolve();
})
.then(function () {
console.log('3');
})
.then(function () {
console.log('4')
});
console.log('5');
5.2 宏任務(wù)中包含微任務(wù)
setTimeout(function () {
console.log('1');
new Promise(function (resolve) {
console.log('2');
resolve();
})
.then(function () {
console.log('3');
})
console.log('4');
});
console.log('5');
5.3 微任務(wù)中包含宏任務(wù)
console.log('1');
new Promise(function (resolve) {
console.log('2');
resolve();
})
.then(function () {
console.log('3');
setTimeout(function () {
console.log('4');
})
console.log('5');
})
console.log('6');
5.4 async 宏任務(wù)
async function fun1() {
console.log('fun1 start')
setTimeout(function () {
console.log('fun1 setTimeout');
});
await fun2();
console.log('fun1 end')
}
async function fun2() {
console.log('fun2 start')
new Promise((resolve)=>{
console.log('fun2 Promise')
resolve()
})
.then(()=>{
console.log('fun2 Promise then')
})
console.log('fun2 end')
}
fun1()
5.5 node 事件執(zhí)行分析
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
5.6 綜合案例
console.log('script start')
async function fun1() {
console.log('fun1 start')
process.nextTick(function() {
console.log('fun1 process nextTick');
})
setTimeout(function () {
console.log('fun1 setTimeout');
new Promise(function (resolve) {
console.log('fun1 Promise');
resolve();
})
.then(function () {
console.log('fun1 Promise then');
setTimeout(function () {
console.log('fun1 Promise then setTimeout');
})
console.log('fun1 Promise then end');
})
});
await fun2();
console.log('fun1 end')
}
async function fun2() {
console.log('fun2 start')
setTimeout(function () {
console.log('fun2 setTimeout');
});
new Promise((resolve)=>{
console.log('fun2 Promise')
resolve()
})
.then(()=>{
console.log('fun2 Promise then')
})
console.log('fun2 end')
}
fun1()
setTimeout(function() {
console.log('setTimeout-000')
}, 0)
new Promise(resolve => {
console.log('Promise')
process.nextTick(function() {
console.log('Promise process nextTick');
})
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
process.nextTick(function() {
console.log('promise2 process nextTick');
})
})
console.log('script end')
六、案例分析
6.1 典型的宏任務(wù)、微任務(wù)
// 同步任務(wù)
console.log('2'); // new promise 實(shí)例化過程
console.log('5');
//
// 將 setTimeout console.log('1'); 放入宏任務(wù)隊(duì)列
// 將Promise 的回調(diào)放入微任務(wù)隊(duì)列
// then console.log('3');
// then console.log('4')
// 先微任務(wù)
console.log('3');
console.log('4')
// 再宏任務(wù)
console.log('1');
// 因此,輸出結(jié)果: 2 5 3 4 1
6.2 宏任務(wù)中包含微任務(wù)
// 同步任務(wù)
console.log('5');
// 將 setTimeout 放入宏任務(wù)隊(duì)列,此時(shí),沒有微任務(wù)執(zhí)行,因此,開始執(zhí)行setTImeout宏任務(wù)
console.log('1');
// new Promise 實(shí)例化 同步執(zhí)行:
console.log('2');
// 將Promise.then 回調(diào)放入微任務(wù)
// 當(dāng)前(setTimeout)的宏任務(wù)事件還沒有執(zhí)行完?。?!
// 注意哈??!當(dāng)前(setTimeout)的宏任務(wù)事件還沒有執(zhí)行完?。?!,事件未跳出當(dāng)前 Loop
console.log('4');
// 執(zhí)行完宏任務(wù),開始執(zhí)行 微任務(wù)
console.log('3');
// 因此,結(jié)果為: 5 1 2 4 3
6.3 微任務(wù)中包含宏任務(wù)
// 同步代碼:
console.log('1');
// new Promise
console.log('2');
console.log('6');
// 微任務(wù):Promise.then
console.log('3');
console.log('5');
// 結(jié)束當(dāng)前 Loop[有上個(gè)例子,這個(gè)就不難理解了]
// 開啟宏任務(wù)
console.log('4');
// 因此,結(jié)果為:1 2 6 3 5 4
6.4 async 宏任務(wù)
// fun1
console.log('fun1 start')
// 將setTimeout 放入宏任務(wù)隊(duì)列
// await fun2(); 進(jìn)入 fun2
console.log('fun2 start')
// new Promise
console.log('fun2 Promise')
// 將 then 放入微任務(wù)
console.log('fun2 end') // 當(dāng)前任務(wù)隊(duì)列
// 有微任務(wù),先執(zhí)行微任務(wù)
console.log('fun2 Promise then')
// 回到 await 處
console.log('fun1 end') // 當(dāng)前 fun1 隊(duì)列
console.log('fun1 setTimeout'); // 最后的宏任務(wù)
6.5 node 事件執(zhí)行分析
// 從上往下:
console.log('1'); // 同步代碼
// setTimeout 宏任務(wù)1
// process.nextTick 微任務(wù)1
console.log('7'); // new Promise
// Promise.then 微任務(wù)2
// setTimeout 宏任務(wù)2
// -- 開始執(zhí)行微任務(wù)
console.log('6');
console.log('8')
// -- 開始宏任務(wù)1
console.log('2');
// process.nextTick 微任務(wù)!!!
console.log('4'); // new Promise
// Promise.then 微任務(wù)!!!
// 到此,當(dāng)前宏任務(wù)已執(zhí)行完畢,有微任務(wù),需要先執(zhí)行微任務(wù)
console.log('3');
console.log('5')
// 執(zhí)行宏任務(wù) 2
console.log('9');
// process.nextTick 微任務(wù)
console.log('11');// new Promise
// Promise.then 微任務(wù)!!!
console.log('10');
console.log('12')
// 因此,結(jié)果為:1 7 6 8 2 4 3 5 9 11 10 12
6.6 綜合案例
// 這個(gè)案例,就應(yīng)用了 await 導(dǎo)致的 then 微任務(wù)細(xì)節(jié),我第一次分析也錯(cuò)了
// 還涉及了process.nextTick node 的執(zhí)行時(shí)機(jī)優(yōu)先
// 開始分析:
console.log('script start')
// fun1() 進(jìn)入 fun1
console.log('fun1 start')
// 生成微任務(wù) process.nextTick(fun1)
// 生成 宏任務(wù) setTimeout (fun1)
await fun2(); // 進(jìn)入 fun2
console.log('fun2 start')
// 生成宏任務(wù) setTimeout (fun2)
console.log('fun2 Promise') // new Promise
// 生成 Promise.then 微任務(wù)
console.log('fun2 end')
// !!!此時(shí),fun2 已經(jīng)有返回值了,不需要等待 fun2 中的事件執(zhí)行,回到 await 處,被標(biāo)記了 await .then 的微任務(wù)
// 因此,執(zhí)行主任務(wù)
console.log('Promise') // new Promise
// 生成 Promise process.nextTick 微任務(wù)
// 生成 Promise1.then 微任務(wù)
// 生成 Promise2.then 微任務(wù)
console.log('script end')
/**
* 分析微任務(wù)隊(duì)列
* 1. 第一個(gè)微任務(wù):process.nextTick(fun1)
* 2. fun2 Promise.then // 容易漏
* 3. await .then 的微任務(wù) !!!!!!!!!!!!!
* 4. Promise process.nextTick 微任務(wù)
* 5. Promise1.then 微任務(wù)
* 6. Promise2.then 微任務(wù)
*
*/
// 根據(jù) Node process 優(yōu)先級(jí),先執(zhí)行 process
console.log('fun1 process nextTick');
console.log('Promise process nextTick');
console.log('fun2 Promise then')
// await.then微任務(wù)[await 后的所有代碼,如果還有任務(wù),具體再分析即可]
console.log('fun1 end')
console.log('promise1')
console.log('promise2') // 執(zhí)行到這,又生成新的 process.nextTick 微任務(wù),又先執(zhí)行
console.log('promise2 process nextTick');
// 沒有微任務(wù)了,開始執(zhí)行宏任務(wù)
console.log('fun1 setTimeout');
console.log('fun1 Promise'); // 生成新的 promise.then 微任務(wù),當(dāng)前宏任務(wù)已執(zhí)行完成,開始執(zhí)行微任務(wù)
console.log('fun1 Promise then'); // 生成新的 宏任務(wù) fun1 Promise then setTimeout
console.log('fun1 Promise then end');
/**
* 此時(shí),分析宏任務(wù)隊(duì)列
* 1. 第一個(gè) 是 fun2 setTimeout
* 2. setTimeout(function () { console.log('setTimeout-000') }, 0)
* 3. fun1 Promise then setTimeout
* */
// 因此, 依次執(zhí)行宏任務(wù)
console.log('fun2 setTimeout');
console.log('setTimeout-000')
console.log('fun1 Promise then setTimeout');
這個(gè)案例比較復(fù)雜,某些事件容易漏掉,因此,建議大家手動(dòng)勾起來,每一個(gè)事件都對(duì)應(yīng)到事件隊(duì)列中,這個(gè)案例,考察兩個(gè)點(diǎn),一個(gè)是 await的處理及 node.process 的優(yōu)先級(jí)。大家弄懂這個(gè)案例,出去面試,手撕這種題目應(yīng)該不是問題了。
總結(jié)起來,宏任務(wù)與微任務(wù)的執(zhí)行順序可以歸納為以下幾點(diǎn):
- 當(dāng)一個(gè)宏任務(wù)執(zhí)行完畢后,會(huì)檢查微任務(wù)隊(duì)列中是否存在微任務(wù)。
- 如果存在微任務(wù),則會(huì)依次執(zhí)行微任務(wù)直到微任務(wù)隊(duì)列為空。
- 然后再從宏任務(wù)隊(duì)列中取出下一個(gè)宏任務(wù)進(jìn)行執(zhí)行。
- 這個(gè)過程會(huì)一直重復(fù),直到宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列都為空。
在實(shí)際開發(fā)中,我們常常會(huì)利用宏任務(wù)與微任務(wù)的執(zhí)行順序來進(jìn)行任務(wù)的調(diào)度。通過將一些耗時(shí)較長(zhǎng)的任務(wù)放在宏任務(wù)中,可以保證其他任務(wù)的及時(shí)執(zhí)行;而將一些需要優(yōu)先執(zhí)行的任務(wù)放在微任務(wù)中,可以保證其優(yōu)先級(jí)更高。
需要注意的是,宏任務(wù)與微任務(wù)的執(zhí)行順序是由瀏覽器的事件循環(huán)機(jī)制控制的,不同的瀏覽器可能存在一些差異。因此,在實(shí)際開發(fā)中,我們應(yīng)該合理地安排宏任務(wù)與微任務(wù)的使用,避免出現(xiàn)一些不可預(yù)期的結(jié)果。
綜上所述,宏任務(wù)與微任務(wù)的執(zhí)行順序是宏任務(wù)隊(duì)列執(zhí)行完畢之后依次執(zhí)行微任務(wù)隊(duì)列,然后再從宏任務(wù)隊(duì)列中取出下一個(gè)宏任務(wù)進(jìn)行執(zhí)行。在實(shí)際開發(fā)中,我們可以根據(jù)這個(gè)執(zhí)行順序來合理地安排任務(wù)的執(zhí)行,以達(dá)到更好的效果。
參考文章:
宏任務(wù)和微任務(wù)的執(zhí)行順序_微任務(wù)和宏任務(wù)的執(zhí)行順序-CSDN博客
宏任務(wù)與微任務(wù)執(zhí)行順序(超詳細(xì)講解)_宏任務(wù)和微任務(wù)誰先執(zhí)行-CSDN博客
微任務(wù)和宏任務(wù)哪個(gè)先執(zhí)行?_宏任務(wù)和微任務(wù)誰先執(zhí)行-CSDN博客文章來源:http://www.zghlxwxcb.cn/news/detail-812511.html
例子出處:宏任務(wù)與微任務(wù)執(zhí)行順序(超詳細(xì)講解)_宏任務(wù)和微任務(wù)誰先執(zhí)行-CSDN博客文章來源地址http://www.zghlxwxcb.cn/news/detail-812511.html
到了這里,關(guān)于宏任務(wù)與微任務(wù)執(zhí)行順序及對(duì)比記錄的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!