前言:
首先我們先回顧一下幾個(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í)行。