事件循環機制:JavaScript被設計為單線程,那如何做到異步的呢?
JavaScript是單線程的語言,單線程是指所有的程序路徑按照一定的順序執行,只有前面的程序執行了,后面的程序才會執行。
也就是說在同一時間,JavaScript只能做一件事情,為了協調瀏覽器產生的各種事件、網絡處理、前端渲染等行為,js的事件循環機制,即EventLoop應運而生。
JavaScript是單線程的原因
js的設計初衷是作為瀏覽器的腳本語言,瀏覽器中涉及到與用戶互動、頻繁操作DOM等動作,如果js設計為多線程,會有很復雜的線程同步問題,即使同步問題被解決,也會降低瀏覽器的響應效率,得不償失,因此,JavaScript被設計為單線程保證瀏覽器動作的一致性。
事件循環(EventLoop)
JavaScript既然被設計為單線程,是如何做到異步的呢?這時就用到了JavaScript的事件循環機制。
如下圖所示為JavaScript事件循環的原理圖。
如圖所示,事件循環是主線程循環讀取任務隊列中的任務,直到所有的任務都被執行。在事件循環中,JavaScript用到了棧、堆以及隊列等數據結構。
- 棧中存放的是執行上下文,有函數被調用時,就會創建上下文存放在執行棧中。
- 堆中表示一個非結構化的內存區域,用來存放對象。隊列是指任務隊列,用于存放異步任務。
- js引擎遇到一個異步事件之后不會一直等待事件的返回結果,而是將事件掛起,繼續執行執行棧中的其他任務。
當異步事件返回結果時,js將異步事件callback函數放入隊列中,被放入隊列中的異步事件不會立即回調,等到當前執行棧中的任務都執行完成,處于閑置狀態的主線程按照隊列順序將處于首位事件的callback函數放入執行棧中,執行該函數的同步代碼,如果遇到了異步事件,同樣也會將其回調函數放入事件隊列中......
如此反復,就形成了一個無限循環,這也是被稱為“事件循環(EventLoop)”的原因。
宏任務(Micro task)和微任務(Macro task)
js事件循環的基本原理已經描述清楚,但是異步任務之間也有所不同。
上面講到,JavaScript在執行異步任務時,回調函數會被放在js的任務隊列中,實際上,回調函數的類別不同,執行的優先級也不同。
不同的優先級被分為兩類,一類是宏任務(Micro task),一類是微任務(Macro task)。
回調函數是微任務時,會被放在微任務隊列,回調函數是宏任務時,會被放在宏任務隊列。微任務的優先級高于宏任務,當主線程的任務執行完成時,會首先去執行微任務隊列中首位的回調函數,當微任務隊列中為空時,才回去執行宏任務隊列中的回調函數。
常見的微任務有:promise,常見的宏任務有setInterval等。
因此,事件循環的執行流程圖如下所示:
? ?