What is the event loop? 學習筆記 == ###### tags: `JavaScript` `event loop` # <font color="#3733FF">情境</font> 你正在研究「倒數計時」的功能,在尋找適合 JavaScript 語法的過程中,認識了一個叫 setTimeout 的函式。自己隨手做了一點嘗試: ```javascript setTimeout(function(){console.log('delay 0 sec')}, 0) console.log('Hello!') ``` 結果畫面如下: ```javascript Hello delay 0 sec ``` 為什麼「延遲 0 秒」的函式明明寫在文件上方,應該先被執行,但在 console 印出的結果,他還是被排在第二順位? # <font color="#3733FF">背後的運作原理是什麼</font> [所以說event loop到底是什麼玩意兒?](https://www.youtube.com/watch?v=8aGhZQkoFbQ) 此篇筆記來自觀看影片和閱讀相關文章的想法紀錄。 從影片中要幾個要知道的keyword (or fun fact?) ![](https://i.imgur.com/DRbuS8J.png) 1. stack 2. web APIs 3. blocking 4. event loop -> task queue ## <font color="#006400">stcak 堆疊</font> :::info one thread == one call stack == one thing at a time ::: JavaScript是一種單執行緒(single threaded)的程式語言,單執行緒(一次做一件事)的Runtime,有單執行緒的呼叫堆疊 為什麼是單執行緒?JS需要操作DOM,渲染畫面,一旦有多執行緒,就必須考慮到不同 thread 同時存取同一變數的情形,也會讓情況變得更加複雜。 CallStack是一種資料結構,比如說進入一段程式,當函式執行,在stack上放上執行的項目,可以從下圖觀察到stack會一直堆疊,當函式回傳結果時,我們取下stack頂端的東西,接著執行完畢,stack裡面就清空。 ![](https://i.imgur.com/mJUxMi8.png =50%x)![](https://i.imgur.com/2dTDyBu.jpg =50%x) ![](https://i.imgur.com/NIyUZWx.png =50%x)![](https://i.imgur.com/raAu1dM.png =50%x) ## <font color="#006400">web APIs</font> web APIs ,which are extra things that the browser provides. DOM, AJAX, time out, things like that 瀏覽器本身就有支援自己環境中的API如:setTimeout、document.getElement、Ajax 這些並不在V8 source(V8引擎)裡。 ## <font color="#006400">blocking 阻塞</font> :::danger what happens when things are show -> blocking ::: 阻塞,其實是在說很慢的程式:請求很慢,跑起來很慢,同時又在堆疊上。 why is this a problem? because. browsers. (因為我們是在瀏覽器裡執行程式的) 作者用下面的假程式碼來舉例: ```javascript var foo = $.getSync('//foo.com') var bar = $.getSync('//bar.com') var qux = $.getSync('//qux.com') console.log(foo) console.log(bar) console.log(qux) ``` 當堆疊中有未處理完的函式導致阻塞產生時,我們沒辦法在瀏覽器執行其他任何動作,瀏覽器也無法重新渲染,必須要等到 request 執行結束後瀏覽器才會繼續運作。 換句話說,在瀏覽器中使用同步會阻塞後面的執行,假設Ajax請求需要 n秒,整個JavaScript引擎就必須等待n 秒才能執行下一個步驟,瀏覽器會停滯 n秒,整個過程就會變得非常緩慢。 ## <font color="#006400">event loop 事件循環</font> :::info the simplest solution we're provided with is asynchronous callbacks -> 非同步處理 the event loop -> one thing at a time, except not really ::: 解法:非同步處理 可以同時做事的原因,但是瀏覽器回提供我們其他的東西,當我們執行程式後,會並行作業 ![](https://i.imgur.com/ySGEP8p.gif) [圖片來源](https://medium.com/@Rahulx1/understanding-event-loop-call-stack-event-job-queue-in-javascript-63dcd2c71ecd) ### <font color>範例一</font> 範例都是用[loupe](http://latentflip.com/loupe/?code=CmNvbnNvbGUubG9nKCJIaSEiKTsKCnNldFRpbWVvdXQoZnVuY3Rpb24gdGltZW91dCgpIHsKICAgIGNvbnNvbGUubG9nKCJwdXNoZXMgdGhlIGNhbGxiYWNrIG9uIHRvIHRoZSBzdGFjayAiKTsKfSwgMTAwMCk7Cgpjb25zb2xlLmxvZygiV2VsY29tZSB0byBsb3VwZS4iKTs%3D!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4%3D)來練習~ setTimeout ```javascript console.log("Hi!"); setTimeout(function timeout() { console.log("pushes the callback on to the stack "); }, 1000); console.log("Welcome to loupe."); ``` 當程式是開始跑之後, call stack上開始堆疊任務,先出現console.log(HI!)。 接著呼叫setTimeout,把函式和延遲時間丟給setTimeout,它是瀏覽器提供給我們的API,接著瀏覽器會啟動計時器,JavaScript 執行環境 (runtime) 將計時工作交辦給瀏覽器以後,就繼續進行自己的動作,目前setTimeout的任務已經完成,就會從stack移除掉。 一秒鐘後,計時完畢,webAPI不能直接將東西丟到stack上,這樣會隨機出現在程式碼之中, 這就是callback queue(又稱task queue)發揮作用的地方,任何web API都會將回調推送到callback queue, 白話來說是,等瀏覽器把事情辦完以後,再把 callback function 丟回 runtime 中執行。 從下圖可以觀察到當stack清空後,timeout()被回推到stack上,接著出現 console.log("pushes the callback on to the stack"),整個程式就完成了。 ![](https://i.imgur.com/LAqdhzu.gif) :::warning The event loop's job is to look at the stack and look at the task queue. If the stack is empty it takes the first thing on the queue and pushes it on to the stack which effectively run it. event loop 的作用是去監控堆疊(call stack)和工作佇列(task queue),當堆疊當中沒有執行項目的時候(空的),便把佇列中的第一個內容推到stack堆疊中去執行,讓堆疊能有效執行。 ::: ### <font color>範例二</font> 試試執行兩個setTimeout,並把延遲時間改為0,觀察程式如何運行。 ```javascript javascript console.log("Hi!"); setTimeout(function timeout() { console.log("pushes the callback on to the stack "); }, 0); setTimeout(function timeout() { console.log("pushes the callback on to the stack "); }, 0); console.log("Welcome to loupe."); ``` 觀察下圖可以發現把延遲時間改為0,只是計時很快完成,完成後還是會把函式timeout()推到callback queue裡,當stack是空的,event loop就會將timeout()再回推到stack上,印出console.log("pushes the callback on to the stack")。 所以setTimeout設為0的例子也一樣,無論原因,都把程式碼的執行堆遲到堆疊的最後或堆疊清空為主,setTimeout 這個 timer 只能保證超過幾毫秒後 「即將會」 執行,但並不能保證在幾毫秒後會 「即刻」 執行。舉例來說,setTimeout(callback, 1000),表示在 1 秒後這個 function 「即將會」 被執行,但並不是說在 1 秒後這個 function 「立即會」 被執行。 ![](https://i.imgur.com/xzSYv9t.gif) 事件循環的特性,必須等到堆疊清空後才可以把call back推到stack上,stack就可以繼續執行,執行後清空任務。事件循環就會啟動,呼叫call back。 白話一點來說就是,所有同步性的工作,瀏覽器會一個個執行,遇到非同步的操作(IO, API call 等等),會先放到一個叫做 task queue 的地方,等到瀏覽器目前沒有其他工作,就會到 task queue 看看有沒有還沒執行的任務,再把它拿出來執行。 作者在影片的16:30還有舉例到ajax的例子,都是以同樣的方式處理 ### <font color>範例三</font> Click Event + setTimeout ```javascript $.on('button', 'click', function onClick() { setTimeout(function timer() { console.log('You clicked the button!'); }, 2000); }); console.log("Hi!"); setTimeout(function timeout() { console.log("Click the button!"); }, 5000); console.log("Welcome to loupe."); ``` 假設在console.log("Hi!")後點擊click,執行順序會是什麼? ![](https://i.imgur.com/NbhyqL8.gif) 建議多看幾次程式跑動,我把理解的動作記錄如下: 1. 執行第一行,click事件會先被丟到web APIs 等待被觸發 2. stack 印出console.log("Hi!") 3. 點擊click me!按鈕,function onClick()在callback queue排隊 (等stack清空,才會啟動event loop 回推到stack執行) 5. 執行第九行程式,stack: 執行setTimeout(function timeout() ... ,接著timeout()在瀏覽器倒數三秒 7. 執行第13行程式,stack 接著印出console.log("Welcome to loupe.") 8. 此時stack已經清空,在callback queue排隊function onClick()推回stack 9. stack接著執行function timer(),並會推到webAPIs倒數計時一秒 10. 此時setTimeout(function timeout()...裡的倒數已經結束,將任務推到callback queue 11. stack是空的,把timeout()丟回stack執行(timer()還在web APis倒數中) 12. 執行第10行程式stack 印出console.log("Click the button!") 13. 接著在callback queue排隊的function timer()就會回到stack中執行 14. 執行第4行程式,stack印出console.log('You clicked the button!') 15. 完成程式 ## <font color>總結</font> 程式透過這些併發行為、非同步請求的實現,將「費時較久」或「須等待事件才能啟動」的任務往後安排,因而能打造流暢的使用者體驗。 以上是我對event loop的初步了解。 當我們說不要阻塞事件循環時,在說的是不要在堆疊上放慢到不行的程式,,因為這樣做的話,畫面的渲染會被阻塞住,這時你並沒辦法選取瀏覽器上的文字、沒辦法點擊瀏覽器上的元件,瀏覽器就無法執行它需要做的工作,重新渲染畫面,也就是建立一個品質好而流暢的UI 如果執行像是圖像處理、做太多動畫時,要是沒有注意到程式碼是如何排進佇列的話,畫面就會變的遲鈍。 但是如果我們是透過非同步的方式執行這些函式的時候,在每一個 cb 從工作佇列(task queue)到堆疊(stack)的過程中,提供了瀏覽器重新渲染的機會。 >作者還有接續舉例了「寫一個非同步執行的 forEach callback」、「模擬瀏覽器 render 的情況」、「滑動捲軸的情況」...等 > >其他更多影片的範例解說很推薦閱讀**參考資料3**的文章! 有其他資訊或沒有描述正確的地方,歡迎大家指點迷津,謝謝 ## <font color>參考資料</font> [1. 所以說event loop到底是什麼玩意兒?| Philip Roberts ](https://www.youtube.com/watch?v=8aGhZQkoFbQ) [2. loupe](http://latentflip.com/loupe/?code=CmNvbnNvbGUubG9nKCJIaSEiKTsKCnNldFRpbWVvdXQoZnVuY3Rpb24gdGltZW91dCgpIHsKICAgIGNvbnNvbGUubG9nKCJwdXNoZXMgdGhlIGNhbGxiYWNrIG9uIHRvIHRoZSBzdGFjayAiKTsKfSwgMTAwMCk7Cgpjb25zb2xlLmxvZygiV2VsY29tZSB0byBsb3VwZS4iKTs%3D!!!PGJ1dHRvbj5DbGljayBtZSE8L2J1dHRvbj4%3D) [3. 理解JavaScript中的事件循環、堆疊、佇列和併發模式](https://pjchender.blogspot.com/2017/08/javascript-learn-event-loop-stack-queue.html) [4. 談談JavaScript中的asynchronous和event queue](https://pjchender.blogspot.com/2016/01/javascriptasynchronousevent-queue.html) [5. 再談Event Loop](https://f2e.kalan.dev/javascript-basic/5.html#micro-tasks) [6. Understanding Event Loop, Call Stack, Event & Job Queue in Javascript](https://medium.com/@Rahulx1/understanding-event-loop-call-stack-event-job-queue-in-javascript-63dcd2c71ecd) [7. 到底 Event Loop 關我啥事?](https://medium.com/infinitegamer/why-event-loop-exist-e8ac9d287044)