JS底層學習筆記 - EventLoop


Posted by m6fish on 2021-05-08

Photo by Sam Moqadam on Unsplash

目標

弄懂 EventLoop 相關的名,並可以用簡短的話來敘述

0618 EDIT: setTimeout 跟 promise 的執行順序比較

參考

  1. JavaScript 中的同步與非同步(上):先成為 callback 大師吧!
  2. Philip Roberts: What the heck is the event loop anyway?
  3. Erin Zimmer: Further Adventures of the Event Loop
  4. node.js - about
  5. 從ES6開始的JavaScript學習生活-異步程式設計與事件迴圈

強烈建議: 觀看完2與3的影片與1的教學文章。
底下筆記僅記錄個人學習上的心得,有使用到非常多影片中的截圖做為輔助。

名詞

  • EventLoop 事件循環
  • 單執行序 Single Thread
  • WebAPI
  • 阻塞 Blocking / 不塞 non-blocking
  • 同步 synchronous / 非同步 asynchronous
  • 呼叫堆疊 Call Stack
  • 事件佇列 Event Queue / Callback Queue
  • 微任務 Microtask
  • 宏任務 Macrotask

前言

作為一個前端攻城屍,開發的過程中很容易會碰到一些名詞,像是什麼 Single Thread、V8 (JavaScript engine)、Node.js、還有最常碰到的同步非同步等奇怪的專有名詞。而且面試的時候還超級喜歡問這些問題的,筆者也是在某個面試被問到之後,發現自己沒辦法好好地用自己的語言敘述,所以就誕生了這篇文章!

而關於理論與實作的抱怨不在今天的主題討論,但相信能多一些知識也是好的。


主題

JavaScript 是一個 Single Thread 程式語言,可以在不同的 Runtime (執行環境)上執行,像是各家瀏覽器、node.js等。為了更增進使用者體驗,能透過使用 web API非同步不阻塞畫面運行,達成程式運行更順暢的效果。而其中任務的執行順序機制,即是 EventLoop

Single Thread & Runtime

  • Single Thread: 單一執行續,指的是一次只能做一件事
  • Runtime: 執行環境,像是各家瀏覽器的JS引擎&Node.js
  • web API: 執行環境提供,與之互動的方式

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

雖然JS是單一執行續,但 RunTime 可以不是,因此可以叫瀏覽器幫忙做一些事情,完成之後通知

就像是自己去買晚餐的時候,點完單跟老闆說我要去隔壁買飲料,待會再回來拿。
自己只有一個人,所以最有效率的狀況就是叫熊貓先點單然後去做其他事,飲料買完之後再回來拿便當。

而不同執行環境,實作的webAPI也不一樣,就像各家瀏覽器有不一定相同的方法名稱。

如果同一份code同時跑在瀏覽器 & node.js上? 當然有機會壞掉

圖片出自-參考1


Heap / Stack

圖片出自-參考2

Heap

  • 處理記憶體分配,ex: 變數

Stack (Call Stack 呼叫堆疊)

  • synchronous 同步執行
  • 反向執行: A 呼叫 B 呼叫 C => 執行順序: C -> B -> A


圖片出自-參考2


Blocking / Non-blocking

阻塞 Blocking

  • 畫面停頓 / 卡住
  • 背景程式正在跑,需要時間結果出來
  • 等洗衣機 / 等鹹酥雞

阿是要等多久!

不塞 non-blocking

  • 等待的時候先去做其他事,完成之後通知
  • 等洗衣機逼逼叫之後再去處理 / 阿我等一下再回來拿


餐點呼叫器


Synchronous / Asynchronous

同步 synchronous / 非同步 asynchronous

Blocking methods execute synchronously and non-blocking methods execute asynchronously. --- node.js

會阻塞的方法以同步方式執行,不會造成阻塞的方法以非同步執行

Synchronous 同步執行

典型的一件事做完後下一件,若正在執行的任務比較耗時,就會造成阻塞。
全世界都在等你

Asynchronous 非同步執行

即點完單人就跑去做其他事,通常會這麼做是因為這件事需要耗時比較久,不想空等,而趁等待的時候可以做其他事情。
從中文字面滿容易誤解,ex: "同步"去做一些事情

這邊提供筆者自己的記憶方式: 同步理解為同步調、同節奏,像是當兵的時候要對腳步1212。這時候你不可能,一邊等任務一邊跑去打小蜜蜂,同時做兩件事就是不同步 脫離部隊掌握

因此實務上通常使用在會需要等回應的地方,例如請瀏覽器5秒後通知你 (setTimeOut),或者打API等Server回應。取得回應之後再去執行 CallBack,才不會造成畫面卡在那邊一直轉圈圈。


Event Queue

事件佇列 Event Queue / Callback Queue / task Queue

不同名詞,描述同一個概念: 非同步的任務放進去 queue

非同步的任務,不直接進入 Stack 執行,而會進到 Queue 等待(到stake可以執行的時候才過去)。
不同runTime可能會有不同的 queue,但同類(依照runTime自己區分)的方法會進到同個 queue

  • asynchronous
  • 可以有很多的 queue
  • queue 會等到 stack 做完拿過去 stack執行

圖片出自-參考2


EventLoop

非同步Task 是怎麼運作的?

一種機制: 隨時檢查 Stack,當其內容為空時,取出 Queue 內任務拿進去 Stack 執行

簡化示意會像這張圖內的程式碼:

此外,如果在瀏覽器上執行,還需要處理頁面渲染。瀏覽器很聰明,不會時時刻刻都去重畫圖,通常是一段時間才刷新畫面(ex: 60 fps -> 16 ms 更新一次),也不可能打斷正在執行的工作,所以重新渲染的條件是: 單一任務完成,且到達渲染時間

由於這樣的機制,如果在做耗時較久的任務時,渲染就會被阻塞。

圖中大水管指的是Browser rendering pipeline,而渲染被"大任務"卡住住到21ms還沒更新
圖片出自-參考3

注: 非同步的任務才會進到 queue 內,同步的任務會在stack就處理完了。但是非同步的callback裡面是可以裝同步的任務,也可以裝耗時很久的任務,渲染一樣會被卡住。

setTimeOut(() => { 
    while(true) {}
}, 500)

額外主題: setTimeout 不一定依照設定的時間執行

僅代表"盡快"在delay內執行

圖片出自-參考2


Macrotask / Microtask

宏任務 Macrotask

  • 非 Microtask 的任務

不過就是出個名詞出來嚇人,網路上有些資源不使用這個詞,而直接使用 task

微任務 Microtask

  • 99.9% 是 promise
  • 接續在每個 Macrotask 之後做
  • 整個 Microtask queue 做完才能渲染

圖片出自-參考3
注:被微任務卡住渲染

子主題: setTimeout 與 Promise 的執行順序

  • setTimeout 放在queue裡面
  • Promise 放在 microtask
  • microtask(同步) -> microtask(非同步) -> queue(非同步)

Promise和setTimeout执行顺序
async await、Promise、setTimeout执行顺序
Promise 实现原理


Animation Frame callback queue

  • 跟 Microtask 一樣有特殊行為
  • 接續在每個 Macrotask 之後
  • 在頁面渲染之前做
  • 當前 queue 裏頭的做完就會渲染 (而非整個queue做完)
    後續可以設定新的動畫frame,而不卡住渲染

圖片出自-參考3


結語

這篇文章不知道會不會太長沒人看XD


#javascript







Related Posts

[JS101] JavaScript - 常用的內建函式

[JS101] JavaScript - 常用的內建函式

OOP 10 - 回顧抽象、封裝、繼承、多型

OOP 10 - 回顧抽象、封裝、繼承、多型

用 JavaScript 學習資料結構和演算法:堆疊(Stack)篇

用 JavaScript 學習資料結構和演算法:堆疊(Stack)篇


Comments