基本概念
為便於理解,在進入正題之前,不得不說以下一些基本的概念。
程式與執行緒
程式(process)
-
是作業系統結構的基礎;
-
是系統進行資源分配和排程的基本單位;
-
在linux系統中可以使用
ps-ef
來查詢程式列表(如下圖)。比程式更小的單位叫做執行緒
。
執行緒(thread)
-
是作業系統能夠進行運算排程的最小單位;
-
它被包含在程式之中,是程式中的實際運作單位;一個程式可以
併發
多個執行緒,每條執行緒並行
執行不同的任務。
併發(concurrency)
- 一個處理器同時處理多個任務。
並行(parallelism)
- 多個處理器或多核處理器同時處理多個任務。
單執行緒
- 單執行緒在程式執行時,所走的程式路徑按照連續順序排下來,前面的必須處理好,後面的才會執行。
同步與非同步
同步(synchronous)
- 呼叫一旦開始,呼叫者必須等到該呼叫結束才能執行下一步操作。
非同步(asynchronous)
- 方法一旦呼叫,就會立即返回,呼叫者便可進行下一步操作。
JS執行機制
執行棧
- 是一個儲存函式呼叫的棧結構,遵循
先進後出
的原則。
主執行緒
- 現在正在執行執行棧中的哪個事件。
Event Loop
以上我們講到了程式與執行緒
,同步與非同步
,執行棧
,主執行緒
等,那麼JS到底是怎麼執行的呢?
先來供上一個常見面試題,寫出以下程式碼的執行結果
console.log('start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('promise')
resolve()
}).then(() => {
console.log('then1')
}).then(() => {
console.log('then2');
})
console.log('end')
複製程式碼
結果如下圖:
為什麼setTimeout最後輸出呢???
JS程式碼的執行其實就是往執行棧中放入函式。那麼遇到非同步程式碼的時候該怎麼辦呢?其實當遇到非同步程式碼時,會被掛起並在需要執行的時候加入到任務佇列
。一旦執行棧為空,Event Loop 就會從任務佇列中拿出需要執行的程式碼並放入執行棧中執行。
JS引擎常駐於記憶體中,等待宿主將JS程式碼或函式傳遞給它,也就是等待宿主環境分配巨集觀任務,反覆等待 - 執行即為事件迴圈。
Event Loop中,每一次迴圈稱為tick,每一次tick的任務如下:
- 執行棧選擇最先進入佇列的巨集任務(一般都是
script
),執行其同步程式碼直至結束; - 檢查是否存在微任務,有則會執行至微任務佇列為空;
- 如有必要會渲染頁面;
- 開始下一輪tick,執行巨集任務中的非同步程式碼(setTimeout的回撥等)。
巨集任務與微任務
巨集任務(macrotask)
- 宿主(Node、瀏覽器)發起的任務;
- 在ES6規範中,將其稱為task;
- script、setTimeout、setInterval、I/O、UI rendering、postMessage、MessageChannel、setImmediate
微任務(microtask)
- JS引擎發起的任務;
- 在ES6規範中,將其稱為jobs;
- Promise、MutaionObserver、process.nextTick
參考
小結
本文主要介紹了JS執行機制,即Event Loop,及其相關的一系列概念。
如有問題,歡迎指正。