JS基礎(chǔ)系列(二)同/異步任務(wù)、宏/微任務(wù)的執(zhí)行順序

? 由于這兩天面試有遇到相關(guān)的問(wèn)題忽冻,以及在維護(hù)外包項(xiàng)目時(shí)遇到的種種相關(guān)的奇葩異步亂用的問(wèn)題,決定好好捋捋這幾個(gè)名詞在實(shí)際中的應(yīng)用此疹。

一甚颂、隊(duì)列類(lèi)型

? js是單線程編程語(yǔ)言蜜猾,所以js的執(zhí)行順序是按語(yǔ)句的順序去排列的。

js的執(zhí)行任務(wù)可以分為兩類(lèi):

  1. 同步任務(wù):就是在主線程上的任務(wù)振诬,順序到達(dá)后馬上執(zhí)行蹭睡;
  2. 異步任務(wù):在主線程上異步執(zhí)行的任務(wù),順序到達(dá)后并不會(huì)馬上執(zhí)行赶么,但會(huì)被排在任務(wù)隊(duì)列里肩豁,執(zhí)行完同步任務(wù)后按隊(duì)列執(zhí)行異步任務(wù)。

二辫呻、執(zhí)行

不管理沒(méi)理解清钥,不廢話,直接剛:

1放闺、下面先看同步任務(wù)A

console.log('start');
function task() {
  console.log('task');
}
task();
console.log('end');

在控制臺(tái)可以看到輸出start task end祟昭,這就是同步任務(wù),只要順序到達(dá)馬上執(zhí)行怖侦。

2篡悟、接著看異步任務(wù)

異步任務(wù)有ES5settimeout、setinterval以及ES6promise匾寝。

settimeout

接著上面的代碼搬葬,先看前者:B

console.log('start');
setTimeout(() => {
  console.log('s1');
});
function task() {
  console.log('task');
}
task();
console.log('end');

可以看到,盡管settimeouttask函數(shù)的前面艳悔,但s1在最后輸出急凰,表明settimeout是異步任務(wù),排在主線程之外的隊(duì)列中執(zhí)行猜年。

? 為了進(jìn)一步驗(yàn)證抡锈,我們?cè)黾与y度,看下面代碼:C

console.log('start');
setTimeout(() => {
  console.log('s1');
});
function task() {
  console.log('task');
  setTimeout(() => {
    console.log('s2');
  });
}
task();
setTimeout(() => {
  console.log('s3');
});
console.log('end');

? 刷新瀏覽器乔外,可以看到控制臺(tái)在最后按順序輸出s1 s2 s3床三,這表明所有的settimeout事件在同一隊(duì)列里,所以隊(duì)列里的settimeout按順序執(zhí)行袁稽。

? 在日常開(kāi)發(fā)過(guò)程中我們經(jīng)常會(huì)遇到異步嵌套異步勿璃,如果同個(gè)隊(duì)列內(nèi)部都有異步擒抛,這時(shí)候的執(zhí)行又是怎樣的呢推汽?接下來(lái)繼續(xù)增加難度:D

console.log('start');
setTimeout(() => {
  console.log('s1');
  setTimeout(() => {
    console.log('s4');
  });
});
function task() {
  console.log('task');
  setTimeout(() => {
    console.log('s2');
  });
}
task();
setTimeout(() => {
  console.log('s3');
  setTimeout(() => {
    console.log('s5');
  });
});
console.log('end');

?上面我們?cè)趦蓚€(gè)settimeout里分別新增了一個(gè)settimeout,這時(shí)候的執(zhí)行順序會(huì)不會(huì)有什么不同呢歧沪?

?繼續(xù)刷新瀏覽器歹撒,在控制臺(tái)看輸出...end s1...s5,怎么樣诊胞,有沒(méi)有覺(jué)得很奇怪暖夭?

?如果不理解js的任務(wù)隊(duì)列執(zhí)行順序問(wèn)題锹杈,會(huì)對(duì)上面的代碼執(zhí)行結(jié)果表示一臉萌,起碼當(dāng)初的我就是這種表情迈着。

?所以接著C的思路在D的體現(xiàn):主線程任務(wù)先執(zhí)行竭望,異步任務(wù)推入任務(wù)隊(duì)列,主線程任務(wù)執(zhí)行完成之后按順序繼續(xù)執(zhí)行任務(wù)隊(duì)列的任務(wù)裕菠;在任務(wù)隊(duì)列里有二維異步任務(wù)咬清,推入第二條隊(duì)列,執(zhí)行完第一隊(duì)列后奴潘,繼續(xù)執(zhí)行第二隊(duì)列旧烧;

?看到這里,應(yīng)該對(duì)js的任務(wù)隊(duì)列有一定的理解了吧画髓,如果還不理解掘剪,就按照上面的例子換著法子使勁折騰就對(duì)了,實(shí)踐出真知奈虾,在學(xué)習(xí)編程的時(shí)候是最最真的道理了夺谁。

?看完settimeout的例子,接下來(lái)我們繼續(xù)看 promise愚墓,至于setinterval就暫時(shí)不討論了予权。

promise

為了循序漸進(jìn),我們接著B的例子一點(diǎn)點(diǎn)增加難度浪册,繼續(xù):E

console.log('start');
setTimeout(() => {
  console.log('s1');
});
function task() {
  console.log('task');
}
new Promise(resolve => {
  console.log('p1');
  resolve(true);
});
task();
console.log('end');

? 刷新瀏覽器可以看到輸出start p1 task end s1扫腺,為啥?

? 原因所在村象,就要引申出宏任務(wù)微任務(wù)的概念了笆环。

? 如果對(duì)js的事件循環(huán)機(jī)制理解不深,到這里或許就要懵了厚者,既有同步任務(wù)異步任務(wù)還有任務(wù)隊(duì)列躁劣,現(xiàn)在又多了了宏觀任務(wù)和微觀任務(wù),這咋判斷库菲?接下來(lái)我就講講這幾個(gè)概念的...我也講不清楚账忘,所以我就找了一段網(wǎng)上我覺(jué)得描述的比較好的:

js是單線程語(yǔ)言,對(duì)于異步操作只能先把它放在一邊熙宇,按照某種規(guī)則按先后順序放進(jìn)一個(gè)容器(其實(shí)就是存入宏觀任務(wù)和微觀任務(wù)隊(duì)列中)鳖擒,先處理同步任務(wù),再處理異步任務(wù)烫止。異步任務(wù)分為 [ 宏觀任務(wù)隊(duì)列蒋荚、微觀任務(wù)隊(duì)列 ]

按照規(guī)定,能發(fā)起宏觀任務(wù)的方法有:

script(整體代碼)馆蠕、setTimeout期升、setInterval惊奇、I/O、UI交互事件播赁、postMessage颂郎、MessageChannel、setImmediate(Node.js 環(huán)境)容为;

微觀任務(wù)的方法有:

Promise.then祖秒、MutaionObserver、process.nextTick(Node.js 環(huán)境)舟奠,async/await實(shí)際上是promise+generator的語(yǔ)法糖竭缝,也就是promise,也就是微觀任務(wù)沼瘫;

? 有promise就少不了then抬纸,所以在E基礎(chǔ)上我們加上then,再看輸出:F

console.log('start');
setTimeout(() => {
  console.log('s1');
});
function task() {
  console.log('task');
}
new Promise(resolve => {
  console.log('p1');
  resolve(true);
}).then(() => {
  console.log('then');
});
task();
console.log('end');

? 之前的輸出是start p1 task end s1耿戚,加了then之后輸出start p1 task end then s1湿故,thens1之前輸出。

? 按照上面對(duì)幾個(gè)概念的描述膜蛔,promise的執(zhí)行屬于微任務(wù)坛猪,settimeout屬于宏任務(wù),而F的輸出表明微任務(wù)先于宏任務(wù)執(zhí)行皂股∈裕可是,這是絕對(duì)的嗎呜呐?下面我們繼續(xù)作:G

console.log('start');
setTimeout(() => {
  console.log('s1');
  new Promise(resolve => {
    console.log('p2');
    resolve(true);
  }).then(() => {
    console.log('then2');
  });
});
function task() {
  console.log('task');
}
new Promise(resolve => {
  console.log('p1');
  resolve(true);
}).then(() => {
  console.log('then');
});
task();
console.log('end');

? 可以看到新添加的微任務(wù)的結(jié)果p2 then2在最后輸出就斤,這是不是跟上面“微任務(wù)先于宏任務(wù)執(zhí)行”有沖突?仔細(xì)一想蘑辑,其實(shí)不然洋机,在上面G的代碼執(zhí)行順序來(lái)看,可以分為幾個(gè)執(zhí)行步驟:

  1. 先執(zhí)行同步任務(wù):start洋魂、p1绷旗、task、end副砍,為何p1會(huì)先執(zhí)行衔肢?因?yàn)檫@時(shí)候的p1其實(shí)還在同步任務(wù)里,then之后的操作才在異步任務(wù)隊(duì)列中;

  2. 接著執(zhí)行異步任務(wù),而異步任務(wù)分為宏任務(wù)和微任務(wù)入篮,微任務(wù)先于宏任務(wù)執(zhí)行,所以第二步執(zhí)行:then启搂、s1

  3. 最后執(zhí)行宏任務(wù)內(nèi)部的異步刘陶,也就是微任務(wù):p2胳赌、then2

    ? 所以從上面可以總結(jié):先執(zhí)行主線程的同步任務(wù)匙隔,這是第一梯隊(duì)疑苫;若有異步,先執(zhí)行異步里的微任務(wù)也就是then內(nèi)部的操作纷责,這是第二梯隊(duì)捍掺;然后執(zhí)行宏任務(wù)也就是settimeout內(nèi)部的操作,這是第三梯隊(duì)再膳;如果第三梯隊(duì)中又有微任務(wù)挺勿,繼續(xù)執(zhí)行,這是第四梯隊(duì)喂柒。

為了驗(yàn)證不瓶,我們繼續(xù)作:H

console.log('123');
setTimeout(() => {
  console.log('s1');
  new Promise(resolve => {
    console.log('res3');
    resolve(true);
  }).then(() => {
    console.log('then3');
  });
});
setTimeout(() => {
  console.log('s2');
}, 0);
function task() {
  console.log('task');
  setTimeout(() => {
    console.log('s3');
  });
  new Promise(resolve => {
    console.log('res2');
    resolve(true);
  }).then(() => {
    console.log('then2');
  });
}
new Promise(resolve => {
  console.log('res');
  setTimeout(() => {
    console.log('ps');
  });
  resolve(true);
  console.log('after');
}).then(() => {
  console.log('then');
});
console.log('456');
task();
console.log('end');

? 看完上面的代碼,先別管結(jié)果如何灾杰,有沒(méi)有想吐槽:變態(tài)蚊丐!誰(shuí)會(huì)寫(xiě)這樣的代碼!你別不信艳吠,其實(shí)這是我在接外包項(xiàng)目debug的時(shí)候經(jīng)常遇到的事麦备。因?yàn)樗麄兊拇a往往是以完成任務(wù)為目標(biāo)而得出來(lái)的,至于完成的過(guò)程是怎樣的昭娩,他們完全不會(huì)關(guān)注泥兰,這就導(dǎo)致代碼內(nèi)部會(huì)出現(xiàn)如宏觀任務(wù)嵌套微任務(wù),微任務(wù)又與宏任務(wù)以及微任務(wù)相互并行的現(xiàn)象题禀,至于中間會(huì)出現(xiàn)什么問(wèn)題鞋诗,They don't care...扯遠(yuǎn)了,想知道上面的結(jié)果是啥迈嘹,自己看削彬。

async...await

? 最后看看promise的語(yǔ)法糖async await在隊(duì)列中又有什么不同, 下面繼續(xù)剛:I

async function async1() {
  console.log('async1-start');
  await async2();
  console.log('async1-end');
}
async function async2() {
  console.log('async2');
  new Promise(function(resolve) {
    console.log('resolve1');
    resolve();
  })
    .then(() => {
    console.log('then');
  })
    .then(() => {
    console.log('then2');
  });
}

console.log('script-start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

async1();

new Promise(function(resolve) {
  console.log('promise1');
  resolve();
})
  .then(function() {
  console.log('promise2');
})
  .then(function() {
  console.log('promise3');
});
console.log('script-end');

? 上面代碼最后輸出是script-start async1-start async2 resolve1 promise1 script-end then async1-end promise2 then2 promise3 setTimeout秀仲。

? 如果不看結(jié)果融痛,自己預(yù)想一遍,能得到正確的輸出嗎神僵?如果理解了代碼從A~H的意思雁刷,基本上是能得到正確答案的,下面我們梳理一下執(zhí)行順序:

  1. 同步任務(wù):

    1. script-start先執(zhí)行--輸出script-start保礼;
    2. 接著執(zhí)行asyn1()函數(shù)沛励,函數(shù)內(nèi)部的async1-start在函數(shù)的同步序列中责语,緊接著被輸出,由于async的緣故目派,后面的語(yǔ)句會(huì)被放到微任務(wù)隊(duì)列-1--輸出async1-start坤候;
    3. 然后到async2()函數(shù),首先會(huì)輸出async2企蹭,接著由于await的原因使得當(dāng)前函數(shù)變?yōu)橥桨壮铮?strong>resolve1雖然在promise內(nèi)部,但還在主線程上谅摄,所以也馬上被輸出徒河,then放在微任務(wù)隊(duì)列-2中--輸出async2 resolve1
    4. 執(zhí)行完asyn1()函數(shù)的同步任務(wù)送漠,繼續(xù)向下執(zhí)行虚青,遇到new Promise,內(nèi)部的promise1在主線程上螺男,馬上被輸出棒厘,then放在微任務(wù)隊(duì)列-3中--輸出promise1
    5. 最后執(zhí)行輸出script-end
  2. 異步任務(wù):

    ? 先執(zhí)行微任務(wù)

    1. 從上面執(zhí)行順序可知現(xiàn)在有微任務(wù)隊(duì)列1/2/3下隧,按理是按照數(shù)字順序執(zhí)行的奢人,但既然這節(jié)討論的是async awiat,它的作用就是把異步函數(shù)當(dāng)成同步處理淆院,也就是說(shuō)等當(dāng)前的異步執(zhí)行完之后何乎,才會(huì)繼續(xù)向下執(zhí)行,所以在這里是先執(zhí)行微任務(wù)2土辩,才會(huì)執(zhí)行微任務(wù)1支救,最后執(zhí)行微任務(wù)3

      至于第二層鏈?zhǔn)?strong>then拷淘,因?yàn)闆](méi)有async await語(yǔ)法讓它提前執(zhí)行各墨,所以放在第二梯隊(duì)的微任務(wù)里。

      所以第一次微任務(wù)--輸出then async1-end promise2启涯;

    2. 上面執(zhí)行完第一梯隊(duì)的微任務(wù)贬堵,接著執(zhí)行第二梯隊(duì)微任務(wù),也就是第二層then語(yǔ)句结洼,所以按順序輸出--then2 promise3黎做;

      執(zhí)行完微任務(wù),最后執(zhí)行宏任務(wù)

    3. 宏任務(wù)只有一個(gè)setTimeout松忍,所以最終輸出--setTimeout蒸殿。

      執(zhí)行完畢。

    最后,如果把await async2()中的await去掉宏所,又會(huì)發(fā)生什么酥艳?請(qǐng)自行驗(yàn)證......

總結(jié)

? 所以綜上所述,簡(jiǎn)單總結(jié)一句話就是:同步任務(wù)結(jié)束后楣铁,先處理微觀任務(wù)后處理宏觀任務(wù),宏觀任務(wù)內(nèi)部處理重復(fù)上述動(dòng)作更扁。

? 以上內(nèi)容個(gè)人實(shí)踐總結(jié)盖腕,如有不對(duì)歡迎拍磚

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市浓镜,隨后出現(xiàn)的幾起案子溃列,更是在濱河造成了極大的恐慌,老刑警劉巖膛薛,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件听隐,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡哄啄,警方通過(guò)查閱死者的電腦和手機(jī)雅任,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)咨跌,“玉大人沪么,你說(shuō)我怎么就攤上這事⌒堪耄” “怎么了禽车?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)刊殉。 經(jīng)常有香客問(wèn)我殉摔,道長(zhǎng),這世上最難降的妖魔是什么记焊? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任逸月,我火速辦了婚禮,結(jié)果婚禮上遍膜,老公的妹妹穿的比我還像新娘彻采。我一直安慰自己,他們只是感情好捌归,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布肛响。 她就那樣靜靜地躺著,像睡著了一般惜索。 火紅的嫁衣襯著肌膚如雪特笋。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,185評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音猎物,去河邊找鬼虎囚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蔫磨,可吹牛的內(nèi)容都是我干的淘讥。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼堤如,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蒲列!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起搀罢,我...
    開(kāi)封第一講書(shū)人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蝗岖,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后榔至,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體抵赢,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年唧取,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铅鲤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡枫弟,死狀恐怖彩匕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情媒区,我是刑警寧澤驼仪,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站袜漩,受9級(jí)特大地震影響绪爸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宙攻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一奠货、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧座掘,春花似錦递惋、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至形真,卻和暖如春杉编,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工邓馒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嘶朱,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓光酣,卻偏偏與公主長(zhǎng)得像疏遏,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子救军,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344