What the heck is the event loop anyway | Philip Roberts | JSConf EU
※ 看完 What the heck is the event loop anyway | Philip Roberts | JSConf EU 之後認識了 Event loop,決定寫這篇筆記來加深印象,更讓自己以後不幸忘記的時候可以看這篇來回想
影片說得非常清楚明白,但我還是想寫一篇文字版的,所以內容都直接引用影片。
JavaScript 是一個單執行緒的程式語言,只有一個 call stack,也就是一次只能做一件事情。
但一次只能做一件事情顯然太不方便了,例如當我們用 JavaScript 來做 Http Request 的時候,還沒得到回應之前,就只能等待回應,不能做其他事情,網頁就會完全卡住。
然而我們在用瀏覽器上網的時候,無時無刻都在發出 Http Request,卻沒有感受到網頁有卡住,是因為回應很快就回來了嗎?快到我們感受不到?
其實是因為有瀏覽器的幫忙。(還沒寫完,先重點放在 Event loop)
Call Stack
Call Satck 是一種資料結構,可以記錄程式執行到哪裡,例如我們進入一個 function,這個 function 就會放在 stack 的最上面,當 function 回傳結果時,會把這個 function 從 stack pop 掉。
接下來看一下這段程式碼:
function multiply(a, b) {
return a * b
}
function square(n) {
return multiply(n, n)
}
function printSquare(n) {
var squared = square(n)
console.log(squared)
}
printSquare(4)
- 程式一開始會先把
main()
放進 stack - 第 1, 5, 9 行分別宣告三個 functions
- 第 14 行執行
printSquare(4)
,所以把printSquare(4)
push 進 stack - 在
printSquare(4)
裡面我們會執行square(n)
,把square(n)
push 進 stack - 在
square(n)
裡面會執行multiply(n, n)
,把multiply(n, n)
push 進 stack multiply(n, n)
會回傳a * b
,把multiply(n, n)
pop 出 stacksquare(n)
回傳multiply(n, n)
也就是a * b
,把square(n)
pop 出 stackprintSquare
執行console.log(squared)
,沒有回傳但是 function 已經結束了,所以 pop 出 stack- 程式執行完畢,把
main()
pop 出 stack
如果我們寫了一個程式碼長這樣:
function foo() {
return foo()
}
foo()
在 foo()
裡面回傳自己,那就會在 call stack 裡面堆滿 foo()
,而且沒辦法釋放,通常 runtime 會避免這種行為,所以會丟出錯誤訊息,中止這個程式。
Blocking
Blocking 沒有一個嚴格的定義,基本上就是在說一段很慢的程式。
console.log()
不慢,執行一個從一到一百億的 while
迴圈有點慢,圖片請求很慢,網路請求很慢,當這些很慢的東西放在 stack 上面,就是 blocking。
看下面這段假的程式碼:
var foo = $.getSync('//foo.com')
var bar = $.getSync('//bar.com')
var qux = $.getSync('//qux.com')
console.log(foo)
console.log(bar)
console.log(qux)
假設沒有非同步請求這種東西,全部都是同步的。
- 呼叫
getSync()
,因為是網路請求,所以很慢,需要時間,請求完成得到回應才會繼續下一個步驟 - 終於得到回應,繼續呼叫第二個
getSync()
,一樣要等待回應 - 成功得到回應,繼續呼叫第三個
getSync()
,一樣要等待回應,而這些請求可能永遠都得不到回應 - 最後這三個 blocking 行為都完成,可以把剩下的
console.log()
執行完,把 stack 清空
影片更清楚
8:03~10:20
所以我們需要非同步 callbacks,但他是怎麼處理的呢?
Event loop
當 JavaScript 執行一個非同步的 function 時,webapis 會接手這件事,並在完成的時候把 callback 放到 callback queue,當 stack 清空時,event loop,會把 callback queue 裡的第一個東西放進 stack。
例如下面的程式碼:
console.log('Hi')
setTimeout(function cb() {
console.log('there')
}, 5000)
console.log('Bye')
- 第 1 段呼叫
console.log('Hi')
,push 到 stack,印出Hi
,pop 出 stack - 第 3 段呼叫
setTimeout()
,push 到 stack,webapis 會設定一個計時器,pop 出 stack - 第 7 段呼叫
console.log('Bye')
,push 到 stack,印出Bye
,pop 出 stack - stack 清空
- 當 webapis 的計時器時間到,會把
cb()
push 進 callback queue - stack 是空的,把
cb()
pop 出 callback queue,並 push 進 stack - 執行
cb()
,印出there
,pop 出 stack - 程式結束
所以其實把 setTimeout()
的時間設為 0,他一樣會在 Hi
跟 Bye
,印出來之後,才會被執行。
setTimeout 跟 ajax 其實都是 webapis,並不是 JavaScript 原生的東西