js運行的環境咱們稱之爲宿主環境,目前有三種運行環境,一種運行在瀏覽器(javaScript),一種運行在服務端(nodejs),另外一種是運行在咱們的客戶端(好比Vscode客戶端就是使用js寫的),所以只要給js配備的相應的執行引擎,js能夠運行在任何環境java
JS引擎node
GUI引擎web
事件監聽線程 (DOM事件,window窗口事件等等)ajax
計時線程(SetTimeout、setInterval 計時器)api
網絡線程:(ajax網絡請求)瀏覽器
事件隊列在不一樣的宿主環境中有所差別,大部分宿主環境會將事件隊列進行細分。在瀏覽器中,事件隊列分爲兩種:bash
MutationObserver 用於監聽某個DOM對象的變化網絡
當執行棧清空時、JS引擎首先會將微任務中的全部任務依次執行結束,若是沒有微任務,執行宏任務數據結構
事件循環分爲三個部分,分別由 瀏覽器宿主,web api 與 事件隊列(也稱任務隊列)組成異步
執行棧
因爲JavaScript 引擎是單線程,同一時間只能執行一個任務,其餘任務都得按照順序排隊等待被執行,只有當前的任務執行完成以後纔會往下執行下一個任務,所以這些任務被排隊在一個單獨的地方。這個地方被稱爲執行棧
執行棧是一個後進先出數據結構,用於存放各類函數的執行環境,每個函數執行以前,它的相關信息會加入到執行棧,函數調用以前,建立執行環境,而後push到執行棧;函數調用以後,銷燬執行環境,並從執行棧頂部推(pop)出去同步任務、異步任務
同步任務:當一個腳本第一次執行的時候,js引擎會解析這段代碼,並將其中的同步代碼按照執行順序加入執行棧中,而後從頭開始執行。若是當前執行的是一個方法,那麼js會向執行棧中添加這個方法的執行環境,而後進入這個執行環境繼續執行其中的代碼。當這個執行環境中的代碼 執行完畢並返回結果後,js會退出這個執行環境並把這個執行環境銷燬,回到上一個方法的執行環境。。這個過程反覆進行,直到執行棧中的代碼所有執行完畢。 一個方法執行會向執行棧中加入這個方法的執行環境,在這個執行環境中還能夠調用其餘方法,甚至是本身,其結果不過是在執行棧中再添加一個執行環境。這個過程能夠是無限進行下去的,除非發生了棧溢出,即超過了所能使用內存的最大值。
異步任務:js引擎遇到一個異步事件後並不會一直等待其返回結果,而是會將這個事件掛起,繼續執行執行棧中的其餘任務。當這個異步事件返回結果後,js會將這個事件加入與當前執行棧不一樣的另外一個隊列,咱們稱之爲事件隊列事件隊列(事件隊列是先進先出的數據結構),被放入事件隊列不會馬上執行其回調,而是等待當前執行棧中的全部任務都執行完畢。異步函數的執行事件,會被宿主環境控制。
事件循環(Event Loop)
主線程處於閒置狀態時,主線程會去查找事件隊列是否有任務。 若是有,那麼主線程會從中取出排在第一位的事件,並把這個事件對應的回調放入執行棧中,而後執行其中的同步代碼…,如此反覆,這樣就造成了一個無限的循環。JS引擎對事件隊列的取出執行方式,以及與宿主環境的配合,稱之爲事件循環。
舉個栗子
<script>
function a() {
console.log("a")
b();
}
function b() {
console.log("b");
c();
}
function c() {
console.log("c")
}
console.log("global");
a();
</script>
複製代碼
運行流程:在JS代碼執行以前<script>
,首先會初始化一個全局執行上下文(也稱執行環境),並push到棧頂,接下來開始在全局執行環境下,執行代碼,首先將三個函數定義,提取到全局上下文裏去,而後繼續執行,等執行到console.log('c')
以後,會調用console對象裏的一個log函數,(調用任何一個函的時候,都會爲這個函數建立一個函數執行上下文),而後建立log的執行上下文,並push入棧,而後就能夠運行這個函數了,(始終記住js引擎永遠執行的是執行棧的最頂部),運行該函數後,控制檯輸出一個"global",而後該函數運行結束,銷燬該執行上下文,並從棧頂被推出(出棧),此時又回到了全局執行環境之下繼續執行,此時又遇到了a函數的調用,調用該函數,建立一個a的函數執行上下文,並push入棧,而後開始執行a函數裏面的代碼,在a函數裏又遇到裏console對象裏的log函數,又爲這個log函數建立一個log的函數執行上下文,並push到棧頂,而後運行log函數,控制檯輸出a,以後log函數執行結束,銷燬log的上下文,回到a的函數執行上下文上,繼續運行,而後又發現b函數的調用,此時建立b的函數執行上下文,並push入棧,而後運行b函數,在b函數裏發現console對象裏的log函數的調用,而後繼續爲這個log函數建立log的函數執行上下文,並push入棧,而後運行該log函數並在控制檯輸出"a",此時該log函數執行結束,銷燬其log的上下文,並從棧頂被推出(出棧pop),此時又回到了b的函數執行上下文環境下執行,繼續運行,發現了c函數的調用,而後爲c函數建立C的函數執行上下文,並push入棧,而後開始執行c裏面的代碼,執行代碼過程當中,發現了console對象裏的log函數被調用,又爲這個log函數建立一個log的函數執行上下文,並push到棧頂,在該log環境裏,運行該函數,輸出"c",以後該log函數運行結束,銷燬其log上下文,並從棧頂被推出(出棧pop),而後回到c的執行環境中,此時已經沒有能夠運行的數據了,則c運行結束,銷燬c的函數執行上下文,並從棧頂被推出(出棧pop),而後回到b的執行環境之中,c的調用結束以後,b也沒有能夠執行的代碼,此時b函數也運行結束,則銷燬b的函數執行上下文,並從棧頂被推出(出棧pop),此時回到a的執行環境執行,a函數在b函數的調用結束後也沒有可再執行的代碼,所以a函數也執行結束,銷燬a的函數執行上下文,並從棧頂被推出(出棧pop),最終執行權又回到了全局執行環境裏,在全局執行環境裏,發現已經沒有可運行的代碼了,而後也銷燬全局執行上下文,並從棧頂推出(出棧pop),此時執行棧裏面都爲空,若是事件隊列有事情,則會把執行事件隊列裏的代碼。