js代碼在瀏覽器當(dāng)中的異步執(zhí)行主要是依靠Call stack(調(diào)用棧)、Event loop(事件循環(huán))、Queue(消息隊(duì)列)這三個(gè)模塊來(lái)完成的。

1、首先加載整體代碼,在調(diào)用棧Call stack中壓入一個(gè)全局的匿名函數(shù)調(diào)用,然后依次執(zhí)行每行代碼。

2、同步代碼按順序執(zhí)行,先壓棧,然后執(zhí)行,然后打印,打印完之后彈出執(zhí)行棧。

3、異步調(diào)用也是先壓入調(diào)用棧,但是這里延時(shí)器內(nèi)部函數(shù)是異步的,所以這里需要關(guān)心內(nèi)部函數(shù)到底做了什么。

4、延時(shí)器為timer1函數(shù)開(kāi)啟了一個(gè)時(shí)長(zhǎng)為1.8s的倒計(jì)時(shí)器,延時(shí)器是單獨(dú)工作的,并不會(huì)受到j(luò)s線程的影響,在延時(shí)器調(diào)用時(shí),倒計(jì)時(shí)就已經(jīng)開(kāi)始了。并且延時(shí)器的調(diào)用就已經(jīng)完成了,因此代碼會(huì)向下繼續(xù)執(zhí)行。

5、下面又是開(kāi)啟延時(shí)器,流程和上面一樣,都是先壓棧,然后開(kāi)啟延時(shí)器,然后彈出執(zhí)行棧。

6、最后一個(gè)console.log的調(diào)用和第一行相同,打印完之后,調(diào)用棧中匿名調(diào)用就結(jié)束了,這個(gè)時(shí)候調(diào)用棧就會(huì)被清空掉。

7、這個(gè)時(shí)候調(diào)用棧里已經(jīng)沒(méi)有工作了,事件循環(huán)(Event Loop)就開(kāi)始工作了。Event Loop的作用就是監(jiān)聽(tīng)調(diào)用棧(Call stack)和消息隊(duì)列(Queue)。一旦調(diào)用棧中的任務(wù)都結(jié)束了,事件循環(huán)就會(huì)從消息隊(duì)列中取出第一個(gè)回調(diào)函數(shù)壓入調(diào)用棧中去執(zhí)行。如果消息隊(duì)列中為空,則暫停執(zhí)行。
8、這個(gè)時(shí)候我們回過(guò)頭來(lái)看之前調(diào)用的倒計(jì)時(shí)器,timer2對(duì)應(yīng)的延時(shí)時(shí)間是1s小于timer1的1.8s,因此timer2的延時(shí)會(huì)先結(jié)束,并且率先進(jìn)入消息隊(duì)列中等待執(zhí)行。



9、而此時(shí)事件循環(huán)將能夠監(jiān)聽(tīng)到消息隊(duì)列發(fā)生了變化,則將按執(zhí)行順序?qū)imer2先壓入執(zhí)行棧中去執(zhí)行,執(zhí)行過(guò)程和之前的執(zhí)行過(guò)程是一致的。

10、而如果遇到層層套娃,異步里面又嵌套了異步也是一樣的道理,將異步函數(shù)先放到異步API當(dāng)中去執(zhí)行,等待本輪執(zhí)行再按照上面的邏輯去執(zhí)行。直到調(diào)用棧和消息隊(duì)列中都沒(méi)有需要執(zhí)行的任務(wù)了,整體的代碼執(zhí)行就結(jié)束了。

總結(jié):
1、如果是調(diào)用棧是一個(gè)正在執(zhí)行的工作表,消息隊(duì)列就可以理解為一個(gè)待辦的工作表。
2、js執(zhí)行引擎會(huì)先執(zhí)行完當(dāng)前調(diào)用棧當(dāng)中的任務(wù),然后通過(guò)事件循環(huán),從待辦的工作表(消息隊(duì)列)里再取一個(gè)任務(wù)出來(lái)執(zhí)行,循環(huán)往復(fù)。
3、這個(gè)過(guò)程中我們隨時(shí)都可以繼續(xù)往消息隊(duì)列中再放入新的任務(wù),消息隊(duì)列會(huì)排隊(duì)等待事件循環(huán)。
以上就是異步調(diào)用在js當(dāng)中的實(shí)現(xiàn)過(guò)程,以及基本的原理,整個(gè)過(guò)程都是通過(guò)消息隊(duì)列和異步循環(huán)來(lái)處理的。

js是單線程的,但是瀏覽器并不是單線程的。具體來(lái)講就是通過(guò)js調(diào)用的很多api并非單線程的,例如延時(shí)器等,它內(nèi)部會(huì)有一個(gè)單獨(dú)的線程負(fù)責(zé)去執(zhí)行,在指定的時(shí)間后將內(nèi)部的回調(diào)函數(shù)放入消息隊(duì)列。我們所說(shuō)的單線程,是執(zhí)行我們代碼的線程是單線程,而這些異步的api會(huì)有他們自己的線程去執(zhí)行他們的一些操作,而此時(shí),js的執(zhí)行線程并不會(huì)去等待他們執(zhí)行完再執(zhí)行下面的代碼。
同步異步并不是我們寫(xiě)代碼的方式,而是我們使用的api是以同步方式還是異步方式來(lái)工作的。
所謂同步就是阻塞,當(dāng)前代碼執(zhí)行完,下面的代碼才會(huì)執(zhí)行。
所謂的異步就是非阻塞,先在待執(zhí)行任務(wù)列表中等待同步代碼執(zhí)行完,再回來(lái)執(zhí)行,并且異步代碼的執(zhí)行并不會(huì)阻塞下面的代碼。