JavaScript執行模型
引言
JavaScript是一個單執行緒(Single-threaded)異步(Asynchronous)非阻塞(Non-blocking)并發(Concurrent)語言,這些語言效果通過一個呼叫堆疊(Call Stack)、一個事件回圈(Event Loop)、一個回呼佇列(Callback Queue)有些時候也叫任務佇列(Task Queue)與跟運行環境相關的API組成,
概念
呼叫堆疊 Call Stack
呼叫堆疊是一個LIFO后進先出資料結構的函式運行堆疊,它內部的資料結構為函式幀,當在JavaScript中呼叫一個函式時,它將被壓入堆疊中,當這個函式內部還有另一個函式被呼叫時,另一個函式將會被壓入堆疊頂,直到其內部沒有更多呼叫,堆疊頂函式將會被以單執行緒方式執行并出堆疊,直到最后一個函式幀出堆疊,JavaScript語言特性中的但形成就是指的呼叫堆疊的單執行緒運行,
function multiply(a, b) {
return a * b;
}
function square(n) {
return multiply(n, n)
}
function printSquare(n) {
console.log(square(n));
}
printSquare(4);
首先呼叫堆疊壓入main(),掃描到printSquare()函式呼叫呼叫堆疊壓入printSquare(4),printSquare函式內部呼叫square(n)該函式被壓入堆疊,同理multiply(n, n)函式也被壓入堆疊且沒有更多呼叫,JavaScript引擎開始執行堆疊頂函式multiply(n, n)回傳結果并出堆疊,以此類推直到main()函式出堆疊,
呼叫堆疊有一個意外情況,當函式遞回呼叫其自身時呼叫堆疊將溢位,執行環境將報錯,
function foo() {
foo();
}
foo();
任務佇列 Task Queue
任務佇列是WebAPI的一部分,也就是說它本身并不是ECMAScript標準的一部分,而是運行環境自行實作的,任務佇列是所有回呼函式排隊執行的FIFO先進先出佇列,它的單位是任務(Task),每個任務都關聯著一個用于處理這個任務的回呼函式,在事件回圈(Event Loop)中會將任務佇列內的函式壓入呼叫堆疊執行并出佇列,直至為空,
任務佇列在瀏覽器的實作中被分為了宏任務佇列(macrotask queue)和微任務佇列(microtask queue),它們分別個自承載宏任務(macrotask)和微任務(microtask)的排隊,其中宏任務佇列與宏任務又被默認為常規的任務佇列與任務,
當呼叫堆疊內所有呼叫都完成執行后,事件輪詢會在每次處理宏任務佇列的一個宏任務后處理微任務佇列的全部微任務,也就是微任務基本會在宏任務處理之前被處理,微任務處理中間不會被UI或網路事件處理被執行,微任務執行是連續的,
會被添加到宏任務的方法的回呼有:
- script:script標簽中的代碼決議運行
setTimeoutsetIntervalsetImmediateI/OUI rendering:UI渲染,每16.6ms放到佇列上一次,60fps,如果呼叫堆疊被占用則會被阻塞
會被添加到微任務的Web API方法有:
process.nextTick:Node提供的PromiseObject.observeMutationObserver
微任務只會從我們撰寫的代碼中產生,宏任務既可能從我們撰寫的代碼中產生也可能從瀏覽器本身事件、渲染、IO產生,
事件回圈 Event Loop
事件回圈是JavaScript的事件處理機制,它會一直輪詢訊息佇列,當滿足呼叫堆疊為空且訊息佇列不為空時,它將把訊息佇列隊頭的訊息壓入執行堆疊,這樣的機制保證了函式不會被中斷,不會有執行緒切換帶來的資料不一致等情況
事件回圈在呼叫堆疊為空時輪詢,順序為
- 找到任務佇列(宏任務佇列)的最早被添加的任務并將其添加到呼叫堆疊執行
- 執行所有微任務佇列內的任務
- 當微任務佇列不為空時找到微任務佇列最早被添加的任務并將其添加到呼叫堆疊執行
- 渲染所有變化
- 如果宏任務佇列為空等待宏任務出現
- 回傳步驟1
JavaScript運行時 Runtime
瀏覽器的JavaScript代碼執行也就是呼叫堆疊與堆(用于儲存變數物件等)由JavaScript引擎提供,用的比較多的是谷歌的V8引擎,Chrome、Edge瀏覽器、Nodejs均使用該引擎,
事件回圈Event Loop、任務佇列Task Queue(回呼佇列Callback Queue)、WebAPI或Node API由運行環境提供,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/216188.html
標籤:其他
