簡介
JS是一種單線程腳本語言,為什么要設(shè)計成單線程介时?
舉例說明没宾,假設(shè)JS是多線程腳本語言,A線程修改了DOM沸柔,B線程刪除了DOM循衰,一旦B線程先執(zhí)行完,DOM被刪除了褐澎,A線程就會報錯会钝,為了避免類似這種問題,JS被設(shè)計為單線程
單線程的問題是一次只能做一件事工三,要做第二件事迁酸,必須等第一件事先做完。假如有個需求是每5分鐘更新一次數(shù)據(jù)俭正,用setInterval去計時奸鬓,那么這個頁面JS永遠(yuǎn)無法做其他事了,線程一直被setInterval占用著掸读。為了讓JS可以同時執(zhí)行多個任務(wù)串远,引入了Event loops(事件循環(huán))機(jī)制
Event loops分為2種隊(duì)列,task隊(duì)列儿惫、microtask隊(duì)列澡罚,業(yè)界一般把tasks隊(duì)列稱為宏任務(wù),microtask翻譯過來叫微任務(wù)姥闪。
task隊(duì)列和microtask隊(duì)列執(zhí)行順序是怎樣的始苇?
代碼剛開始執(zhí)行時,整體代碼就是一個task筐喳,立即執(zhí)行這個task,在執(zhí)行過程中
- 遇到setTimeout函喉、setInterval避归、I/O、setImmediate(Nodejs環(huán)境)就往task隊(duì)列里push
- 遇到Promise.then/catch/finally管呵、MutationObserver梳毙、process.nextTick(Nodejs環(huán)境)就往microtask隊(duì)列里push
每執(zhí)行完一個task,就會查看microtask隊(duì)列里有沒有待執(zhí)行的任務(wù)捐下,如果有账锹,則按先進(jìn)先出的原則依次執(zhí)行其中的任務(wù)萌业,執(zhí)行完了再回到task隊(duì)列,取下一個task執(zhí)行奸柬;如果沒有生年,就直接執(zhí)行下一個task,以此類推廓奕,這就是Event loops抱婉,類似于遞歸執(zhí)行過程
案例
按照以上規(guī)則,思考以下代碼輸出順序
// 先自己思考一下輸出順序
console.log('script start');
setTimeout(function () {
console.log('timeout');
}, 0);
Promise.resolve().then(function () {
console.log('promise');
}).then(function () {
console.log('then');
});
console.log('script end');
分析:
- 整體代碼做為第一個task桌粉,從上到下開始執(zhí)行
- 輸出
script start
- 遇到
setTimeout()
蒸绩,push到task隊(duì)列,等待執(zhí)行 - 遇到
Promise
第一個then()
铃肯,push到microtask隊(duì)列患亿,等待執(zhí)行 - 輸出
script end
- 第一個task執(zhí)行完成,查看microtask隊(duì)列押逼,有任務(wù)窍育,開始執(zhí)行
- 輸出
promise
,遇到第二個then()
宴胧,push到microtask隊(duì)列 - 輸出剛剛push的
then
- microtask隊(duì)列執(zhí)行完成漱抓,取下一個task執(zhí)行
- 輸出
timeout
輸出順序?yàn)椋簊cript start -> script end -> promise -> then -> timeout
升級,return Promise
將上面例子的Promise升級了一下恕齐,假設(shè)Promise.then內(nèi)部又有Promise乞娄,怎么分析?
Promise.resolve().then(function () {
console.log('promise');
return new Promise((resolve, reject) => {
console.log('inner promise');
resolve();
}).then(() => {
console.log('inner then1');
}).then(() => {
console.log('inner then2');
})
}).then(function () {
console.log('then');
});
分析:
- 整體代碼為第一個task显歧,從上到下開始執(zhí)行
- 遇到第一個then仪或,push到microtask隊(duì)列
- 第一個task執(zhí)行完成,查看microtask隊(duì)列士骤,有任務(wù)范删,開始執(zhí)行
- 輸出
promise
- 進(jìn)入內(nèi)部new Promise,輸出
inner promise
- 遇到內(nèi)部new Promise第一個then拷肌,push到microtask隊(duì)列
- 輸出剛剛push的
inner then1
- 遇到內(nèi)部new Promise第二個then到旦,push到microtask隊(duì)列
- 輸出剛剛push的
inner then2
- 內(nèi)部new Promise執(zhí)行完,外部promise第一個then拿到返回值巨缘,繼續(xù)往下添忘,遇到它的第二個then,push到microtask隊(duì)列
- 輸出剛剛push的
then
輸出順序?yàn)椋簆romise -> inner promise -> inner then1 -> inner then2 -> then
注意:then鏈?zhǔn)秸{(diào)用時若锁,如果前面的then方法return了一個新Promise對象搁骑,后面的then會等待這個新Promise對象狀態(tài)發(fā)生變化后,才會執(zhí)行,換句話說仲器,兩個then的執(zhí)行由異步變成同步了煤率,如果把return去掉呢?
變化乏冀,無return Promise
Promise.resolve().then(function () {
console.log('promise');
new Promise((resolve, reject) => {
console.log('inner promise');
resolve();
}).then(() => {
console.log('inner then1');
}).then(() => {
console.log('inner then2');
})
}).then(function () {
console.log('then');
});
分析:
- 整體代碼為第一個task蝶糯,從上到下開始執(zhí)行
- 遇到第一個then,push到microtask隊(duì)列
- 第一個task執(zhí)行完成煤辨,查看microtask隊(duì)列裳涛,有任務(wù),開始執(zhí)行
- 輸出
promise
- 進(jìn)入內(nèi)部new Promise众辨,輸出
inner promise
- 遇到內(nèi)部new Promise第一個then端三,push到microtask隊(duì)列,前6步跟上面一樣
- 此時鹃彻,外部Promise對象的第一個then里的同步代碼已經(jīng)執(zhí)行完了郊闯,接著執(zhí)行它的第二個then,push到microtask隊(duì)列
- 繼續(xù)執(zhí)行microtask蛛株,輸出
inner then1
- 到了內(nèi)部new Promise的第二個then团赁,push到microtask隊(duì)列
- 繼續(xù)執(zhí)行microtask,輸出
then
- 最后輸出
inner then2
輸出順序?yàn)椋簆romise -> inner promise -> inner then1 -> then -> inner then2
總結(jié):return去掉之后谨履,前面的then執(zhí)行完同步代碼就會跳到下一個then
思考
最后思考一個問題欢摄,下面這個錯誤能被catch捕獲到嗎?為什么笋粟?
new Promise(function (resolve, reject) {
setTimeout(function () {
throw new Error('test')
}, 0)
resolve('ok');
}).catch(err => {
console.error(err);
});
熟悉了Event loops怀挠,回答這個問題就很容易:不能捕獲到。因?yàn)殄e誤在setTimeout內(nèi)部拋出害捕,setTimeout和.catch并不在同一個task執(zhí)行锰提,拋出錯誤的時候年枕,catch已經(jīng)執(zhí)行完了套才。
覺得不錯漠趁,點(diǎn)個star吧Github