事件循環(huán)機(jī)制(Event loop)宏任務(wù)(macro-task)微任務(wù)(micro-task)

前言:

首先我們先回顧一下幾個(gè)簡(jiǎn)單的知識(shí)點(diǎn)

1 進(jìn)程與線程的關(guān)系

進(jìn)程:程序的一次執(zhí)行,他占有一片獨(dú)有的內(nèi)存空間

線程:CPU的基本調(diào)度單位,是程序執(zhí)行的一個(gè)完整流程

關(guān)系:

一個(gè)進(jìn)程中一般至少有一個(gè)運(yùn)行的線程——主線程

一個(gè)進(jìn)程中也可以同時(shí)運(yùn)行多個(gè)線程

多個(gè)進(jìn)程之間的數(shù)據(jù)是不能同時(shí)直接共享的

2 JS 是單線程還是多線程?

先看一段代碼塊


// 最后執(zhí)行順序 結(jié)果: 正常1,f2 ,正常2,f1,定時(shí)器;

眾所周知:JS是一門(mén)單線程語(yǔ)言 就像上面的代碼塊 任務(wù)是一個(gè)一個(gè)列隊(duì)的形式被調(diào)用的

在最新的HTML5中提出了Web-Worker,但javascript是單線程這一核心仍未改變。

既然js是單線程?那就像只有一個(gè)窗口的銀行,客戶需要排隊(duì)一個(gè)一個(gè)辦理業(yè)務(wù),同理js任務(wù)也要一個(gè)一個(gè)順序執(zhí)行。如果一個(gè)任務(wù)耗時(shí)過(guò)長(zhǎng),那么后一個(gè)任務(wù)也必須等著

3 為什么js是單線程的?

假設(shè)JS是多線程的 會(huì)發(fā)生什么

試想一下 如果javascript是多線程的,那么當(dāng)兩個(gè)線程同時(shí)對(duì)dom進(jìn)行一項(xiàng)操作,

例如一個(gè)向其添加事件,而另一個(gè)刪除了這個(gè)dom,此時(shí)該如何處理呢?

因此,為了保證不會(huì) 發(fā)生類似于這個(gè)例子中的情景,javascript選擇只用一個(gè)主線程來(lái)執(zhí)行代碼,這樣就保證了程序執(zhí)行的一致性。

4 那么既然是單線程 為什么定時(shí)器可以沒(méi)有按照順序執(zhí)行呢?

為什么在接口請(qǐng)求的時(shí)候 可以同時(shí)發(fā)起多個(gè)請(qǐng)求呢?

這里就涉及到 同步和異步情況這也正是我們今天的重點(diǎn)內(nèi)容,事件循環(huán)機(jī)制(event loop)

?帶著這個(gè)問(wèn)題 我們進(jìn)入下面的內(nèi)容

正文:

1.執(zhí)行棧與事件隊(duì)列

執(zhí)行棧:當(dāng)我們調(diào)用一個(gè)方法的時(shí)候,js會(huì)生成一個(gè)與這個(gè)方法對(duì)應(yīng)的執(zhí)行環(huán)境(context),又叫執(zhí)行上下文。

這個(gè)執(zhí)行環(huán)境中存在著這個(gè)方法的私有作用域,上層作用域的指向,方法的參數(shù),這個(gè)作用域中定義的變量以及這個(gè)作用域的this對(duì)象。

而當(dāng)一系列方法被依次調(diào)用的時(shí)候,因?yàn)閖s是單線程的,同一時(shí)間只能執(zhí)行一個(gè)方法,于是這些方法被排隊(duì)在一個(gè)單獨(dú)的地方。這個(gè)地方被稱為執(zhí)行棧。

事件隊(duì)列:當(dāng)一個(gè)腳本第一次執(zhí)行的時(shí)候,js引擎會(huì)解析這段代碼,并將其中的同步代碼按照?qǐng)?zhí)行順序加入執(zhí)行棧中,然后從頭開(kāi)始執(zhí)行。

如果當(dāng)前執(zhí)行的是一個(gè)方法,那么js會(huì)向執(zhí)行棧中添加這個(gè)方法的執(zhí)行環(huán)境,然后進(jìn)入這個(gè)執(zhí)行環(huán)境繼續(xù)執(zhí)行其中的代碼。

當(dāng)這個(gè)執(zhí)行環(huán)境中的代碼 執(zhí)行完畢并返回結(jié)果后,js會(huì)退出這個(gè)執(zhí)行環(huán)境并把這個(gè)執(zhí)行環(huán)境銷毀,回到上一個(gè)方法的執(zhí)行環(huán)境。

這個(gè)過(guò)程反復(fù)進(jìn)行,直到執(zhí)行棧中的代碼全部執(zhí)行完畢。

一個(gè)方法執(zhí)行會(huì)向執(zhí)行棧中加入這個(gè)方法的執(zhí)行環(huán)境,在這個(gè)執(zhí)行環(huán)境中還可以調(diào)用其他方法,甚至是自己,其結(jié)果不過(guò)是在執(zhí)行棧中再添加一個(gè)執(zhí)行環(huán)境。

這個(gè)過(guò)程可以是無(wú)限進(jìn)行下去的,除非發(fā)生了棧溢出,即超過(guò)了所能使用內(nèi)存的最大值。(這就是為什么遞歸容易出現(xiàn)棧溢出的原因)

以上的過(guò)程說(shuō)的都是同步代碼的執(zhí)行。那么當(dāng)一個(gè)異步代碼(如發(fā)送ajax請(qǐng)求數(shù)據(jù))執(zhí)行后會(huì)如何呢?

js引擎遇到一個(gè)異步事件后并不會(huì)一直等待其返回結(jié)果,而是會(huì)將這個(gè)事件掛起,繼續(xù)執(zhí)行執(zhí)行棧中的其他任務(wù)。

當(dāng)一個(gè)異步事件返回結(jié)果后,js會(huì)將這個(gè)事件加入與當(dāng)前執(zhí)行棧不同的另一個(gè)隊(duì)列,我們稱之為事件隊(duì)列。

事件循環(huán):?被放入事件隊(duì)列不會(huì)立刻執(zhí)行其回調(diào),而是等待當(dāng)前執(zhí)行棧中的所有任務(wù)都執(zhí)行完畢, 主線程處于閑置狀態(tài)時(shí),主線程會(huì)去查找事件隊(duì)列是否有任務(wù)。

如果有,那么主線程會(huì)從中取出排在第一位的事件,并把這個(gè)事件對(duì)應(yīng)的回調(diào)放入執(zhí)行棧中,然后執(zhí)行其中的同步代碼...,如此反復(fù),

這樣就形成了一個(gè)無(wú)限的循環(huán)。這就是這個(gè)過(guò)程被稱為?事件循環(huán)(Event Loop);


在代碼執(zhí)行的時(shí)候 是先執(zhí)行同步代碼?再執(zhí)行異步代碼的;

圖上標(biāo)志的JS模塊,是我們編寫(xiě)的代碼。

按照順序執(zhí)行完后(即同步代碼執(zhí)行完),

其代碼里定時(shí)器的回調(diào)函數(shù),Dom事件的回調(diào)函數(shù),Ajax請(qǐng)求的回調(diào)函數(shù)等這些異步代碼,

會(huì)由瀏覽器WebAPIs管理模塊里的對(duì)應(yīng)模塊相應(yīng)管理。當(dāng)這些回調(diào)函數(shù),到達(dá)要執(zhí)行的條件(比如定時(shí)器到時(shí)間了,用戶點(diǎn)擊了),會(huì)由相應(yīng)的模塊送到隊(duì)列里,然后執(zhí)行。

在列隊(duì)(callBack Queue)里面 -誰(shuí)先放進(jìn)去,就誰(shuí)先執(zhí)行;

接下來(lái)我們看下面一段代碼塊


分析:

定時(shí)器的第二個(gè)參數(shù)是0,即立馬放進(jìn)了隊(duì)列里,

Promise也是立馬達(dá)到了滿足條件。也會(huì)放進(jìn)隊(duì)列里,又根據(jù)代碼的先后執(zhí)行順序,隊(duì)列里肯定先放定時(shí)器的回調(diào)函數(shù),再放Promise的回調(diào)函數(shù),

等同步代碼執(zhí)行完后,異步代碼就是先執(zhí)行定時(shí)器里回調(diào)函數(shù),再執(zhí)行Promise里的回調(diào)函數(shù)。控制臺(tái)輸出應(yīng)該是1 3 2才對(duì)?。。?!

可是為啥是 1 2 3 呢???

這就引出了我們今天所學(xué)的第二個(gè)重點(diǎn)內(nèi)容?宏任務(wù)(macro-task)與微任務(wù)(micro-task)

先看下面這個(gè)圖

**==** (ps:上圖的標(biāo)注的分線程是瀏覽器的,不是我們的Js代碼的。我們只是寫(xiě)的代碼交給了瀏覽器管理);

在圖里面我們可以看到隊(duì)列里又進(jìn)行了劃分,又分為宏隊(duì)列與微隊(duì)列

宏隊(duì)列里放的是宏任務(wù)

微隊(duì)列里放的是微任務(wù)

其Promise 和 Mutation(vue里會(huì)遇到mutation)里的回調(diào)函數(shù),會(huì)放進(jìn)微對(duì)列

微隊(duì)列優(yōu)先級(jí)是高于宏隊(duì)列優(yōu)先級(jí)

so: 這個(gè)就可以解釋上線的代碼塊輸出的是 1 2 3了

下面我們?cè)倏匆欢未a塊


因?yàn)椋好繄?zhí)行一個(gè)宏任務(wù)時(shí),都會(huì)檢查微隊(duì)列中是否有待執(zhí)行的的回調(diào),優(yōu)先執(zhí)行微任務(wù) 也就是微任務(wù)是可以插隊(duì)的

結(jié)尾:

javascrit的事件循環(huán)是這門(mén)語(yǔ)言中非常重要且基礎(chǔ)的概念。

清楚的了解了事件循環(huán)的執(zhí)行順序和每一個(gè)階段的特點(diǎn),可以使我們對(duì)一段異步代碼的執(zhí)行順序有一個(gè)清晰的認(rèn)識(shí),

從而減少代碼運(yùn)行的不確定性。合理的使用各種延遲事件的方法,有助于代碼更好的按照其優(yōu)先級(jí)去執(zhí)行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容