Event Loop

在了解Event Loop之前須先知曉一些JavaScript的觀念

單執行緒(Single Thread)

JavaScript程式的特性是單執行緒,也就是同一時間只能做一件事,而且一次只會執行一個程式碼片段(one thing at a time、EC)。

Call Stack

由於JavaScript一次只做一件事,因此透過Call Stack來紀錄目前程式執行到哪個部分,當執行到某一個函式時,便將此函式push到stack; 而當從一個函式中返回時,便將此函式pop出stack。

function multiply(a, b) {
	return a * b
}

function square(n) {
	return multiply(n, n)
}

function printSquare(n) {
	let squared = square(n)
	console.log(squared)
}

printSquare(4)

執行堆疊流程範例

阻塞(blocking)

一般程式碼的執行順序都是由上往下依序執行,一個執行程序完成後才會在接下一個,這就是同步(Synchronous),但是當程式碼需要等待很長一段時間才能結束時,像是處理資料庫查詢的執行程序時,這時其他的操作像是點選按鈕都會像卡住一樣沒有作用,必須等到此處程式return並從stack中離開後才能往下執行,造成阻塞的情形。

var foo = $.getSync('//foo.com')
var bar = $.getSync('//bar.com')
var qux = $.getSync('//qux.com')

console.log(foo)
console.log(bar)
console.log(qux)

執行堆疊流程範例

Async Callback

為了解決阻塞的問題,可以使用Async callback(非同步回調)函式的做法,來暫緩callback函式的執行,並在之後的某個時間再回到主執行緒執行。

console.log('hi')

setTimeout(function () {
	console.log('there')
}, 5000)

console.log('JSConfEU')

在以上的範例中,如果以同步的執行順序應該是hi->there->JSConfEU,但真正的結果是hi->JSConfEU->there,因為當程式執行到setTimeout這行程式時,會先被移出main thread外面,而等到main thread執行完stack的程式後,在某個時間才會再將此callback函式移回main thread中執行輸出的動作。

執行堆疊流程範例

Event Loop

JavaScript在Runtime時一次只能做一件事,但瀏覽器提供了更多不同的Web API讓我們使用,使得同時間可以處理許多事情,稱之為並發(Concurrency),而使用Event Loop搭配非同步(Asynchronous)的方式即可達成此效果。下圖是出自Philip Roberts: What the heck is the event loop anyway?|JSConf EU 2014,為Event Loop的概念圖,也可將Code丟到loupe網站測試。

Event_Loop

  • 在瀏覽器中程式碼依序執行(heap)時,會將function丟到call stack中執行,此時執行順序是先進後出(FILO)

  • 而當遇到非同步函式像是WebAPI時,main thread會將此函式移到外面執行,原本的stack仍會繼續執行

  • 當WebAPI執行完後根據完成的時間依序將callback移到queue中,等待stack中執行完為空的時,便會將queue的第一個項目移回stack執行,此queue的執行順序是先進先出(FIFO),Event Loop(事件迴圈)便會不斷地在stack和queue之間檢查是否要將任務移回main thread執行

Event Loop執行流程範例

猜猜以下範例結果

function aFunc(value, cb){
	setTimeout(cb, 1000, value)
}

function bFunc(value, cb){
	setTimeout(cb, 1000, value)
}

aFunc('a', function cbA(value){console.log(value)})
bFunc('b', function cbB(value){console.log(value)})

a

b

Part 2

function aFunc(value, cb){
	setTimeout(cb, 1000, value)
}

function bFunc(value, cb){
	setTimeout(cb, 900, value)
}

function cbA(value){
	console.log(value)
}

function cbB(value){
	console.log(value)
}

aFunc('a', cbA)
bFunc('b', cbB)

b

a

//差別是在有時間差

小結

Javascript是單執行緒的,利用Event Loop機制來達到Never blocking(絕不阻塞)。

Ref