前言:
首先我們先回顧一下幾個簡單的知識點
1 進(jìn)程與線程的關(guān)系
進(jìn)程:程序的一次執(zhí)行拢操,他占有一片獨有的內(nèi)存空間
線程:CPU的基本調(diào)度單位莹痢,是程序執(zhí)行的一個完整流程
關(guān)系:
一個進(jìn)程中一般至少有一個運行的線程——主線程
一個進(jìn)程中也可以同時運行多個線程
多個進(jìn)程之間的數(shù)據(jù)是不能同時直接共享的
2 JS 是單線程還是多線程琼娘?
先看一段代碼塊
// 最后執(zhí)行順序 結(jié)果: 正常1预侯,f2 嗤攻,正常2腺怯,f1袱饭,定時器;
眾所周知:JS是一門單線程語言 就像上面的代碼塊 任務(wù)是一個一個列隊的形式被調(diào)用的
在最新的HTML5中提出了Web-Worker呛占,但javascript是單線程這一核心仍未改變虑乖。
既然js是單線程?那就像只有一個窗口的銀行,客戶需要排隊一個一個辦理業(yè)務(wù)晾虑,同理js任務(wù)也要一個一個順序執(zhí)行疹味。如果一個任務(wù)耗時過長,那么后一個任務(wù)也必須等著
3 為什么js是單線程的帜篇?
假設(shè)JS是多線程的 會發(fā)生什么
試想一下 如果javascript是多線程的糙捺,那么當(dāng)兩個線程同時對dom進(jìn)行一項操作,
例如一個向其添加事件笙隙,而另一個刪除了這個dom洪灯,此時該如何處理呢?
因此竟痰,為了保證不會 發(fā)生類似于這個例子中的情景签钩,javascript選擇只用一個主線程來執(zhí)行代碼掏呼,這樣就保證了程序執(zhí)行的一致性。
4 那么既然是單線程 為什么定時器可以沒有按照順序執(zhí)行呢铅檩?
為什么在接口請求的時候 可以同時發(fā)起多個請求呢憎夷?
這里就涉及到 同步和異步情況這也正是我們今天的重點內(nèi)容,事件循環(huán)機(jī)制(event loop)
?帶著這個問題 我們進(jìn)入下面的內(nèi)容
正文:
1.執(zhí)行棧與事件隊列
執(zhí)行棧:當(dāng)我們調(diào)用一個方法的時候昧旨,js會生成一個與這個方法對應(yīng)的執(zhí)行環(huán)境(context)拾给,又叫執(zhí)行上下文。
這個執(zhí)行環(huán)境中存在著這個方法的私有作用域兔沃,上層作用域的指向蒋得,方法的參數(shù),這個作用域中定義的變量以及這個作用域的this對象粘拾。
而當(dāng)一系列方法被依次調(diào)用的時候窄锅,因為js是單線程的,同一時間只能執(zhí)行一個方法缰雇,于是這些方法被排隊在一個單獨的地方。這個地方被稱為執(zhí)行棧追驴。
事件隊列:當(dāng)一個腳本第一次執(zhí)行的時候械哟,js引擎會解析這段代碼,并將其中的同步代碼按照執(zhí)行順序加入執(zhí)行棧中殿雪,然后從頭開始執(zhí)行暇咆。
如果當(dāng)前執(zhí)行的是一個方法,那么js會向執(zhí)行棧中添加這個方法的執(zhí)行環(huán)境丙曙,然后進(jìn)入這個執(zhí)行環(huán)境繼續(xù)執(zhí)行其中的代碼爸业。
當(dāng)這個執(zhí)行環(huán)境中的代碼 執(zhí)行完畢并返回結(jié)果后,js會退出這個執(zhí)行環(huán)境并把這個執(zhí)行環(huán)境銷毀亏镰,回到上一個方法的執(zhí)行環(huán)境扯旷。
這個過程反復(fù)進(jìn)行,直到執(zhí)行棧中的代碼全部執(zhí)行完畢索抓。
一個方法執(zhí)行會向執(zhí)行棧中加入這個方法的執(zhí)行環(huán)境钧忽,在這個執(zhí)行環(huán)境中還可以調(diào)用其他方法,甚至是自己逼肯,其結(jié)果不過是在執(zhí)行棧中再添加一個執(zhí)行環(huán)境耸黑。
這個過程可以是無限進(jìn)行下去的,除非發(fā)生了棧溢出篮幢,即超過了所能使用內(nèi)存的最大值大刊。(這就是為什么遞歸容易出現(xiàn)棧溢出的原因)
以上的過程說的都是同步代碼的執(zhí)行。那么當(dāng)一個異步代碼(如發(fā)送ajax請求數(shù)據(jù))執(zhí)行后會如何呢三椿?
js引擎遇到一個異步事件后并不會一直等待其返回結(jié)果缺菌,而是會將這個事件掛起葫辐,繼續(xù)執(zhí)行執(zhí)行棧中的其他任務(wù)。
當(dāng)一個異步事件返回結(jié)果后男翰,js會將這個事件加入與當(dāng)前執(zhí)行棧不同的另一個隊列另患,我們稱之為事件隊列。
事件循環(huán):?被放入事件隊列不會立刻執(zhí)行其回調(diào)蛾绎,而是等待當(dāng)前執(zhí)行棧中的所有任務(wù)都執(zhí)行完畢昆箕, 主線程處于閑置狀態(tài)時,主線程會去查找事件隊列是否有任務(wù)租冠。
如果有鹏倘,那么主線程會從中取出排在第一位的事件,并把這個事件對應(yīng)的回調(diào)放入執(zhí)行棧中顽爹,然后執(zhí)行其中的同步代碼...纤泵,如此反復(fù),
這樣就形成了一個無限的循環(huán)镜粤。這就是這個過程被稱為?事件循環(huán)(Event Loop)捏题;
在代碼執(zhí)行的時候 是先執(zhí)行同步代碼?再執(zhí)行異步代碼的;
圖上標(biāo)志的JS模塊肉渴,是我們編寫的代碼公荧。
按照順序執(zhí)行完后(即同步代碼執(zhí)行完),
其代碼里定時器的回調(diào)函數(shù)同规,Dom事件的回調(diào)函數(shù)循狰,Ajax請求的回調(diào)函數(shù)等這些異步代碼,
會由瀏覽器WebAPIs管理模塊里的對應(yīng)模塊相應(yīng)管理券勺。當(dāng)這些回調(diào)函數(shù)绪钥,到達(dá)要執(zhí)行的條件(比如定時器到時間了,用戶點擊了)关炼,會由相應(yīng)的模塊送到隊列里程腹,然后執(zhí)行。
在列隊(callBack Queue)里面 -誰先放進(jìn)去盗扒,就誰先執(zhí)行;
接下來我們看下面一段代碼塊
分析:
定時器的第二個參數(shù)是0跪楞,即立馬放進(jìn)了隊列里,
Promise也是立馬達(dá)到了滿足條件侣灶。也會放進(jìn)隊列里甸祭,又根據(jù)代碼的先后執(zhí)行順序,隊列里肯定先放定時器的回調(diào)函數(shù)褥影,再放Promise的回調(diào)函數(shù)池户,
等同步代碼執(zhí)行完后,異步代碼就是先執(zhí)行定時器里回調(diào)函數(shù),再執(zhí)行Promise里的回調(diào)函數(shù)校焦∩薅叮控制臺輸出應(yīng)該是1 3 2才對啊U洹7昭!
可是為啥是 1 2 3 呢耸成?报亩??
這就引出了我們今天所學(xué)的第二個重點內(nèi)容井氢?宏任務(wù)(macro-task)與微任務(wù)(micro-task)
先看下面這個圖
**==** (ps:上圖的標(biāo)注的分線程是瀏覽器的弦追,不是我們的Js代碼的。我們只是寫的代碼交給了瀏覽器管理);
在圖里面我們可以看到隊列里又進(jìn)行了劃分花竞,又分為宏隊列與微隊列
宏隊列里放的是宏任務(wù)
微隊列里放的是微任務(wù)
其Promise 和 Mutation(vue里會遇到mutation)里的回調(diào)函數(shù)劲件,會放進(jìn)微對列里
微隊列優(yōu)先級是高于宏隊列優(yōu)先級
so: 這個就可以解釋上線的代碼塊輸出的是 1 2 3了
下面我們再看一段代碼塊
因為:每執(zhí)行一個宏任務(wù)時,都會檢查微隊列中是否有待執(zhí)行的的回調(diào)约急,優(yōu)先執(zhí)行微任務(wù) 也就是微任務(wù)是可以插隊的
結(jié)尾:
javascrit的事件循環(huán)是這門語言中非常重要且基礎(chǔ)的概念零远。
清楚的了解了事件循環(huán)的執(zhí)行順序和每一個階段的特點,可以使我們對一段異步代碼的執(zhí)行順序有一個清晰的認(rèn)識厌蔽,
從而減少代碼運行的不確定性遍烦。合理的使用各種延遲事件的方法,有助于代碼更好的按照其優(yōu)先級去執(zhí)行躺枕。